Browse Source

Merge branch 'dev' of github.com:0xchocolate/flipperzero-firmware-with-wifi-marauder-companion into feature_wifi_marauder_app

0xchocolate 2 năm trước cách đây
mục cha
commit
7241f9801e
100 tập tin đã thay đổi với 4672 bổ sung1907 xóa
  1. 1 1
      .github/CODEOWNERS
  2. 3 1
      .github/workflows/build.yml
  3. 1 0
      .gitignore
  4. 9 3
      .gitmodules
  5. 1 1
      .pvsoptions
  6. 22 25
      .vscode/example/launch.json
  7. 3 2
      .vscode/extensions.json
  8. 20 2
      SConstruct
  9. 4 4
      applications/debug/unit_tests/subghz/subghz_test.c
  10. 22 0
      applications/external/hid_app/hid.c
  11. 2 0
      applications/external/hid_app/hid.h
  12. 1 0
      applications/external/hid_app/views.h
  13. 214 0
      applications/external/hid_app/views/hid_mouse_clicker.c
  14. 14 0
      applications/external/hid_app/views/hid_mouse_clicker.h
  15. 1 1
      applications/external/hid_app/views/hid_mouse_jiggler.c
  16. 17 0
      applications/external/mfkey32/application.fam
  17. BIN
      applications/external/mfkey32/images/mfkey.png
  18. BIN
      applications/external/mfkey32/mfkey.png
  19. 1349 0
      applications/external/mfkey32/mfkey32.c
  20. 1 1
      applications/external/picopass/scenes/picopass_scene_read_card_success.c
  21. 1 0
      applications/external/weather_station/protocols/protocol_items.c
  22. 1 0
      applications/external/weather_station/protocols/protocol_items.h
  23. 299 0
      applications/external/weather_station/protocols/wendox_w6726.c
  24. 80 0
      applications/external/weather_station/protocols/wendox_w6726.h
  25. 3 3
      applications/external/weather_station/views/weather_station_receiver.c
  26. 24 17
      applications/main/bad_usb/helpers/ducky_script_commands.c
  27. 2 2
      applications/main/nfc/scenes/nfc_scene_mfkey_complete.c
  28. 2 3
      applications/main/subghz/helpers/subghz_custom_event.h
  29. 1 1
      applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c
  30. 60 0
      applications/main/subghz/helpers/subghz_threshold_rssi.c
  31. 43 0
      applications/main/subghz/helpers/subghz_threshold_rssi.h
  32. 521 0
      applications/main/subghz/helpers/subghz_txrx.c
  33. 290 0
      applications/main/subghz/helpers/subghz_txrx.h
  34. 164 0
      applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c
  35. 96 0
      applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h
  36. 27 0
      applications/main/subghz/helpers/subghz_txrx_i.h
  37. 7 0
      applications/main/subghz/helpers/subghz_types.h
  38. 2 2
      applications/main/subghz/scenes/subghz_scene_delete.c
  39. 1 1
      applications/main/subghz/scenes/subghz_scene_delete_raw.c
  40. 7 11
      applications/main/subghz/scenes/subghz_scene_need_saving.c
  41. 69 160
      applications/main/subghz/scenes/subghz_scene_read_raw.c
  42. 48 74
      applications/main/subghz/scenes/subghz_scene_receiver.c
  43. 78 55
      applications/main/subghz/scenes/subghz_scene_receiver_config.c
  44. 35 77
      applications/main/subghz/scenes/subghz_scene_receiver_info.c
  45. 1 2
      applications/main/subghz/scenes/subghz_scene_region_info.c
  46. 10 10
      applications/main/subghz/scenes/subghz_scene_rpc.c
  47. 7 9
      applications/main/subghz/scenes/subghz_scene_save_name.c
  48. 2 2
      applications/main/subghz/scenes/subghz_scene_save_success.c
  49. 2 2
      applications/main/subghz/scenes/subghz_scene_saved.c
  50. 46 209
      applications/main/subghz/scenes/subghz_scene_set_type.c
  51. 5 6
      applications/main/subghz/scenes/subghz_scene_show_error.c
  52. 1 1
      applications/main/subghz/scenes/subghz_scene_start.c
  53. 15 31
      applications/main/subghz/scenes/subghz_scene_transmitter.c
  54. 24 71
      applications/main/subghz/subghz.c
  55. 90 313
      applications/main/subghz/subghz_i.c
  56. 23 53
      applications/main/subghz/subghz_i.h
  57. 12 11
      applications/main/subghz/views/receiver.c
  58. 1 1
      applications/main/subghz/views/receiver.h
  59. 4 4
      applications/main/subghz/views/subghz_read_raw.c
  60. 1 1
      applications/main/subghz/views/subghz_read_raw.h
  61. 3 3
      applications/main/subghz/views/transmitter.c
  62. 1 1
      applications/main/subghz/views/transmitter.h
  63. 0 2
      applications/services/cli/cli_commands.c
  64. 35 6
      applications/services/desktop/desktop.c
  65. 6 0
      applications/services/desktop/desktop.h
  66. 1 2
      applications/services/desktop/desktop_settings.h
  67. 74 0
      applications/services/desktop/helpers/pin.c
  68. 11 0
      applications/services/desktop/helpers/pin.h
  69. 0 140
      applications/services/desktop/helpers/pin_lock.c
  70. 0 21
      applications/services/desktop/helpers/pin_lock.h
  71. 1 18
      applications/services/desktop/scenes/desktop_scene_lock_menu.c
  72. 2 2
      applications/services/desktop/scenes/desktop_scene_locked.c
  73. 5 3
      applications/services/desktop/scenes/desktop_scene_main.c
  74. 4 3
      applications/services/desktop/scenes/desktop_scene_pin_input.c
  75. 0 1
      applications/services/desktop/views/desktop_events.h
  76. 5 2
      applications/services/desktop/views/desktop_view_debug.c
  77. 1 13
      applications/services/desktop/views/desktop_view_lock_menu.c
  78. 0 2
      applications/services/desktop/views/desktop_view_lock_menu.h
  79. 0 79
      applications/services/desktop/views/desktop_view_pin_setup_done.c
  80. 0 15
      applications/services/desktop/views/desktop_view_pin_setup_done.h
  81. 2 0
      applications/services/gui/modules/menu.c
  82. 0 4
      applications/services/gui/view.c
  83. 9 0
      applications/services/loader/application.fam
  84. 7 0
      applications/services/loader/firmware_api/firmware_api.cpp
  85. 207 347
      applications/services/loader/loader.c
  86. 4 9
      applications/services/loader/loader.h
  87. 117 0
      applications/services/loader/loader_cli.c
  88. 48 31
      applications/services/loader/loader_i.h
  89. 187 0
      applications/services/loader/loader_menu.c
  90. 30 0
      applications/services/loader/loader_menu.h
  91. 30 22
      applications/services/rpc/rpc.c
  92. 73 0
      applications/services/rpc/rpc_desktop.c
  93. 3 0
      applications/services/rpc/rpc_i.h
  94. 1 0
      applications/services/storage/storage_external_api.c
  95. 1 0
      applications/services/storage/storages/storage_ext.c
  96. 6 2
      applications/settings/about/about.c
  97. 2 2
      applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c
  98. 1 1
      applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c
  99. 2 2
      applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c
  100. 3 1
      applications/settings/power_settings_app/views/battery_info.c

+ 1 - 1
.github/CODEOWNERS

@@ -56,7 +56,7 @@
 
 # Lib
 /lib/ST25RFAL002/ @skotopes @DrZlo13 @hedger @gornekich
-/lib/STM32CubeWB/ @skotopes @DrZlo13 @hedger @gornekich
+/lib/stm32wb_copro/ @skotopes @DrZlo13 @hedger @gornekich
 /lib/digital_signal/ @skotopes @DrZlo13 @hedger @gornekich
 /lib/infrared/ @skotopes @DrZlo13 @hedger @gsurkov
 /lib/lfrfid/ @skotopes @DrZlo13 @hedger @nminaylov

+ 3 - 1
.github/workflows/build.yml

@@ -60,7 +60,7 @@ jobs:
       - name: 'Bundle scripts'
         if: ${{ !github.event.pull_request.head.repo.fork }}
         run: |
-          tar czpf artifacts/flipper-z-any-scripts-${SUFFIX}.tgz scripts debug
+          tar czpf artifacts/flipper-z-any-scripts-${SUFFIX}.tgz scripts
 
       - name: 'Build the firmware'
         run: |
@@ -193,12 +193,14 @@ jobs:
           TARGET="$(echo '${{ matrix.target }}' | sed 's/f//')"; \
           ./fbt TARGET_HW=$TARGET DEBUG=0 COMPACT=1 fap_dist updater_package
           echo "sdk-file=$(ls dist/${{ matrix.target }}-*/flipper-z-${{ matrix.target }}-sdk-*.zip)" >> $GITHUB_OUTPUT
+          echo "hw-target-code=$TARGET" >> $GITHUB_OUTPUT
 
       - name: Deploy uFBT with SDK
         uses: flipperdevices/flipperzero-ufbt-action@v0.1.0
         with:
           task: setup
           sdk-file: ${{ steps.build-fw.outputs.sdk-file }}
+          sdk-hw-target: ${{ steps.build-fw.outputs.hw-target-code }}
 
       - name: Build test app with SDK
         run: |

+ 1 - 0
.gitignore

@@ -2,6 +2,7 @@
 *.swp
 *.swo
 *.gdb_history
+*.old
 
 
 # LSP

+ 9 - 3
.gitmodules

@@ -1,9 +1,6 @@
 [submodule "lib/mlib"]
 	path = lib/mlib
 	url = https://github.com/P-p-H-d/mlib.git
-[submodule "lib/STM32CubeWB"]
-	path = lib/STM32CubeWB
-	url = https://github.com/Flipper-Zero/STM32CubeWB.git
 [submodule "lib/littlefs"]
 	path = lib/littlefs
 	url = https://github.com/littlefs-project/littlefs.git
@@ -34,3 +31,12 @@
 [submodule "lib/heatshrink"]
 	path = lib/heatshrink
 	url = https://github.com/flipperdevices/heatshrink.git
+[submodule "lib/st_cmsis_device_wb"]
+	path = lib/stm32wb_cmsis
+	url = https://github.com/STMicroelectronics/cmsis_device_wb
+[submodule "lib/stm32wbxx_hal_driver"]
+	path = lib/stm32wb_hal
+	url = https://github.com/STMicroelectronics/stm32wbxx_hal_driver
+[submodule "lib/stm32wb_copro"]
+	path = lib/stm32wb_copro
+	url = https://github.com/flipperdevices/stm32wb_copro.git

+ 1 - 1
.pvsoptions

@@ -1 +1 @@
---ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -e lib/STM32CubeWB -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/* -e applications/external/dap_link/lib/free-dap
+--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/* -e applications/external/dap_link/lib/free-dap

+ 22 - 25
.vscode/example/launch.json

@@ -11,11 +11,10 @@
             "args": {
                 "useSingleResult": true,
                 "env": {
-                    "PATH": "${workspaceFolder};${env:PATH}",
-                    "FBT_QUIET": 1
+                    "PATH": "${workspaceFolder}${command:extension.commandvariable.envListSep}${env:PATH}"
                 },
-                "command": "fbt get_blackmagic",
-                "description": "Get Blackmagic device",
+                "command": "fbt -s get_blackmagic",
+                "description": "Get Blackmagic device"
             }
         }
     ],
@@ -28,20 +27,21 @@
             "type": "cortex-debug",
             "servertype": "openocd",
             "device": "stlink",
-            "svdFile": "./debug/STM32WB55_CM4.svd",
+            "svdFile": "./scripts/debug/STM32WB55_CM4.svd",
             // If you're debugging early in the boot process, before OS scheduler is running,
             // you have to comment out the following line.
             "rtos": "FreeRTOS",
             "configFiles": [
                 "interface/stlink.cfg",
-                "./debug/stm32wbx.cfg",
+                "./scripts/debug/stm32wbx.cfg",
             ],
             "postAttachCommands": [
+                "source scripts/debug/flipperversion.py",
+                "fw-version",
                 // "compare-sections",
-                "source debug/flipperapps.py",
+                "source scripts/debug/flipperapps.py",
                 "fap-set-debug-elf-root build/latest/.extapps",
-                // "source debug/FreeRTOS/FreeRTOS.py",
-                // "svd_load debug/STM32WB55_CM4.svd"
+                // "source scripts/debug/FreeRTOS/FreeRTOS.py",
             ]
             // "showDevDebugOutput": "raw",
         },
@@ -53,14 +53,16 @@
             "type": "cortex-debug",
             "servertype": "external",
             "gdbTarget": "${input:BLACKMAGIC}",
-            "svdFile": "./debug/STM32WB55_CM4.svd",
+            "svdFile": "./scripts/debug/STM32WB55_CM4.svd",
             "rtos": "FreeRTOS",
             "postAttachCommands": [
                 "monitor swdp_scan",
                 "attach 1",
                 "set confirm off",
                 "set mem inaccessible-by-default off",
-                "source debug/flipperapps.py",
+                "source scripts/debug/flipperversion.py",
+                "fw-version",
+                "source scripts/debug/flipperapps.py",
                 "fap-set-debug-elf-root build/latest/.extapps",
                 // "compare-sections",
             ]
@@ -75,10 +77,12 @@
             "servertype": "jlink",
             "interface": "swd",
             "device": "STM32WB55RG",
-            "svdFile": "./debug/STM32WB55_CM4.svd",
+            "svdFile": "./scripts/debug/STM32WB55_CM4.svd",
             "rtos": "FreeRTOS",
             "postAttachCommands": [
-                "source debug/flipperapps.py",
+                "source scripts/debug/flipperversion.py",
+                "fw-version",
+                "source scripts/debug/flipperapps.py",
                 "fap-set-debug-elf-root build/latest/.extapps",
             ]
             // "showDevDebugOutput": "raw",
@@ -91,27 +95,20 @@
             "type": "cortex-debug",
             "servertype": "openocd",
             "device": "cmsis-dap",
-            "svdFile": "./debug/STM32WB55_CM4.svd",
+            "svdFile": "./scripts/debug/STM32WB55_CM4.svd",
             "rtos": "FreeRTOS",
             "configFiles": [
                 "interface/cmsis-dap.cfg",
-                "./debug/stm32wbx.cfg",
+                "./scripts/debug/stm32wbx.cfg",
             ],
             "postAttachCommands": [
-                "source debug/flipperapps.py",
+                "source scripts/debug/flipperversion.py",
+                "fw-version",
+                "source scripts/debug/flipperapps.py",
                 "fap-set-debug-elf-root build/latest/.extapps",
             ],
             // "showDevDebugOutput": "raw",
         },
-        {
-            "name": "fbt debug",
-            "type": "python",
-            "request": "launch",
-            "program": "./lib/scons/scripts/scons.py",
-            "args": [
-                "plugin_dist"
-            ]
-        },
         {
             "name": "python debug",
             "type": "python",

+ 3 - 2
.vscode/extensions.json

@@ -8,11 +8,12 @@
 		"amiralizadeh9480.cpp-helper",
 		"marus25.cortex-debug",
 		"zxh404.vscode-proto3",
-		"augustocdias.tasks-shell-input"
+		"augustocdias.tasks-shell-input",
+		"rioj7.command-variable"
 	],
 	// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
 	"unwantedRecommendations": [
 		"twxs.cmake",
 		"ms-vscode.cmake-tools"
 	]
-}
+}

+ 20 - 2
SConstruct

@@ -239,19 +239,31 @@ distenv.PhonyTarget(
 )
 
 # Debug alien elf
+debug_other_opts = [
+    "-ex",
+    "source ${FBT_DEBUG_DIR}/PyCortexMDebug/PyCortexMDebug.py",
+    # "-ex",
+    # "source ${FBT_DEBUG_DIR}/FreeRTOS/FreeRTOS.py",
+    "-ex",
+    "source ${FBT_DEBUG_DIR}/flipperversion.py",
+    "-ex",
+    "fw-version",
+]
+
 distenv.PhonyTarget(
     "debug_other",
     "${GDBPYCOM}",
     GDBOPTS="${GDBOPTS_BASE}",
     GDBREMOTE="${OPENOCD_GDB_PIPE}",
-    GDBPYOPTS='-ex "source ${FBT_DEBUG_DIR}/PyCortexMDebug/PyCortexMDebug.py" ',
+    GDBPYOPTS=debug_other_opts,
 )
 
 distenv.PhonyTarget(
     "debug_other_blackmagic",
     "${GDBPYCOM}",
     GDBOPTS="${GDBOPTS_BASE}  ${GDBOPTS_BLACKMAGIC}",
-    GDBREMOTE="$${BLACKMAGIC_ADDR}",
+    GDBREMOTE="${BLACKMAGIC_ADDR}",
+    GDBPYOPTS=debug_other_opts,
 )
 
 
@@ -335,3 +347,9 @@ vscode_dist = distenv.Install("#.vscode", distenv.Glob("#.vscode/example/*"))
 distenv.Precious(vscode_dist)
 distenv.NoClean(vscode_dist)
 distenv.Alias("vscode_dist", vscode_dist)
+
+# Configure shell with build tools
+distenv.PhonyTarget(
+    "env",
+    "@echo $( ${FBT_SCRIPT_DIR}/toolchain/fbtenv.sh $)",
+)

+ 4 - 4
applications/debug/unit_tests/subghz/subghz_test.c

@@ -407,7 +407,7 @@ MU_TEST(subghz_decoder_ido_test) {
         "Test decoder " SUBGHZ_PROTOCOL_IDO_NAME " error\r\n");
 }
 
-MU_TEST(subghz_decoder_keelog_test) {
+MU_TEST(subghz_decoder_keeloq_test) {
     mu_assert(
         subghz_decoder_test(
             EXT_PATH("unit_tests/subghz/doorhan_raw.sub"), SUBGHZ_PROTOCOL_KEELOQ_NAME),
@@ -676,7 +676,7 @@ MU_TEST(subghz_encoder_nice_flo_test) {
         "Test encoder " SUBGHZ_PROTOCOL_NICE_FLO_NAME " error\r\n");
 }
 
-MU_TEST(subghz_encoder_keelog_test) {
+MU_TEST(subghz_encoder_keeloq_test) {
     mu_assert(
         subghz_encoder_test(EXT_PATH("unit_tests/subghz/doorhan.sub")),
         "Test encoder " SUBGHZ_PROTOCOL_KEELOQ_NAME " error\r\n");
@@ -813,7 +813,7 @@ MU_TEST_SUITE(subghz) {
     MU_RUN_TEST(subghz_decoder_gate_tx_test);
     MU_RUN_TEST(subghz_decoder_hormann_hsm_test);
     MU_RUN_TEST(subghz_decoder_ido_test);
-    MU_RUN_TEST(subghz_decoder_keelog_test);
+    MU_RUN_TEST(subghz_decoder_keeloq_test);
     MU_RUN_TEST(subghz_decoder_kia_seed_test);
     MU_RUN_TEST(subghz_decoder_nero_radio_test);
     MU_RUN_TEST(subghz_decoder_nero_sketch_test);
@@ -852,7 +852,7 @@ MU_TEST_SUITE(subghz) {
     MU_RUN_TEST(subghz_encoder_came_twee_test);
     MU_RUN_TEST(subghz_encoder_gate_tx_test);
     MU_RUN_TEST(subghz_encoder_nice_flo_test);
-    MU_RUN_TEST(subghz_encoder_keelog_test);
+    MU_RUN_TEST(subghz_encoder_keeloq_test);
     MU_RUN_TEST(subghz_encoder_linear_test);
     MU_RUN_TEST(subghz_encoder_linear_delta3_test);
     MU_RUN_TEST(subghz_encoder_megacode_test);

+ 22 - 0
applications/external/hid_app/hid.c

@@ -11,6 +11,7 @@ enum HidDebugSubmenuIndex {
     HidSubmenuIndexMedia,
     HidSubmenuIndexTikTok,
     HidSubmenuIndexMouse,
+    HidSubmenuIndexMouseClicker,
     HidSubmenuIndexMouseJiggler,
 };
 
@@ -32,6 +33,9 @@ static void hid_submenu_callback(void* context, uint32_t index) {
     } else if(index == HidSubmenuIndexTikTok) {
         app->view_id = BtHidViewTikTok;
         view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewTikTok);
+    } else if(index == HidSubmenuIndexMouseClicker) {
+        app->view_id = HidViewMouseClicker;
+        view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseClicker);
     } else if(index == HidSubmenuIndexMouseJiggler) {
         app->view_id = HidViewMouseJiggler;
         view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseJiggler);
@@ -53,6 +57,7 @@ static void bt_hid_connection_status_changed_callback(BtStatus status, void* con
     hid_keyboard_set_connected_status(hid->hid_keyboard, connected);
     hid_media_set_connected_status(hid->hid_media, connected);
     hid_mouse_set_connected_status(hid->hid_mouse, connected);
+    hid_mouse_clicker_set_connected_status(hid->hid_mouse_clicker, connected);
     hid_mouse_jiggler_set_connected_status(hid->hid_mouse_jiggler, connected);
     hid_tiktok_set_connected_status(hid->hid_tiktok, connected);
 }
@@ -114,6 +119,12 @@ Hid* hid_alloc(HidTransport transport) {
             hid_submenu_callback,
             app);
     }
+    submenu_add_item(
+        app->device_type_submenu,
+        "Mouse Clicker",
+        HidSubmenuIndexMouseClicker,
+        hid_submenu_callback,
+        app);
     submenu_add_item(
         app->device_type_submenu,
         "Mouse Jiggler",
@@ -172,6 +183,15 @@ Hid* hid_app_alloc_view(void* context) {
     view_dispatcher_add_view(
         app->view_dispatcher, HidViewMouse, hid_mouse_get_view(app->hid_mouse));
 
+    // Mouse clicker view
+    app->hid_mouse_clicker = hid_mouse_clicker_alloc(app);
+    view_set_previous_callback(
+        hid_mouse_clicker_get_view(app->hid_mouse_clicker), hid_exit_confirm_view);
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        HidViewMouseClicker,
+        hid_mouse_clicker_get_view(app->hid_mouse_clicker));
+
     // Mouse jiggler view
     app->hid_mouse_jiggler = hid_mouse_jiggler_alloc(app);
     view_set_previous_callback(
@@ -205,6 +225,8 @@ void hid_free(Hid* app) {
     hid_media_free(app->hid_media);
     view_dispatcher_remove_view(app->view_dispatcher, HidViewMouse);
     hid_mouse_free(app->hid_mouse);
+    view_dispatcher_remove_view(app->view_dispatcher, HidViewMouseClicker);
+    hid_mouse_clicker_free(app->hid_mouse_clicker);
     view_dispatcher_remove_view(app->view_dispatcher, HidViewMouseJiggler);
     hid_mouse_jiggler_free(app->hid_mouse_jiggler);
     view_dispatcher_remove_view(app->view_dispatcher, BtHidViewTikTok);

+ 2 - 0
applications/external/hid_app/hid.h

@@ -20,6 +20,7 @@
 #include "views/hid_keyboard.h"
 #include "views/hid_media.h"
 #include "views/hid_mouse.h"
+#include "views/hid_mouse_clicker.h"
 #include "views/hid_mouse_jiggler.h"
 #include "views/hid_tiktok.h"
 
@@ -43,6 +44,7 @@ struct Hid {
     HidKeyboard* hid_keyboard;
     HidMedia* hid_media;
     HidMouse* hid_mouse;
+    HidMouseClicker* hid_mouse_clicker;
     HidMouseJiggler* hid_mouse_jiggler;
     HidTikTok* hid_tiktok;
 

+ 1 - 0
applications/external/hid_app/views.h

@@ -4,6 +4,7 @@ typedef enum {
     HidViewKeyboard,
     HidViewMedia,
     HidViewMouse,
+    HidViewMouseClicker,
     HidViewMouseJiggler,
     BtHidViewTikTok,
     HidViewExitConfirm,

+ 214 - 0
applications/external/hid_app/views/hid_mouse_clicker.c

@@ -0,0 +1,214 @@
+#include "hid_mouse_clicker.h"
+#include <gui/elements.h>
+#include "../hid.h"
+
+#include "hid_icons.h"
+
+#define TAG "HidMouseClicker"
+#define DEFAULT_CLICK_RATE 1
+#define MAXIMUM_CLICK_RATE 60
+
+struct HidMouseClicker {
+    View* view;
+    Hid* hid;
+    FuriTimer* timer;
+};
+
+typedef struct {
+    bool connected;
+    bool running;
+    int rate;
+    HidTransport transport;
+} HidMouseClickerModel;
+
+static void hid_mouse_clicker_start_or_restart_timer(void* context) {
+    furi_assert(context);
+    HidMouseClicker* hid_mouse_clicker = context;
+
+    if(furi_timer_is_running(hid_mouse_clicker->timer)) {
+        furi_timer_stop(hid_mouse_clicker->timer);
+    }
+
+    with_view_model(
+        hid_mouse_clicker->view,
+        HidMouseClickerModel * model,
+        {
+            furi_timer_start(
+                hid_mouse_clicker->timer, furi_kernel_get_tick_frequency() / model->rate);
+        },
+        true);
+}
+
+static void hid_mouse_clicker_draw_callback(Canvas* canvas, void* context) {
+    furi_assert(context);
+    HidMouseClickerModel* model = context;
+
+    // Header
+    if(model->transport == HidTransportBle) {
+        if(model->connected) {
+            canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
+        } else {
+            canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
+        }
+    }
+
+    canvas_set_font(canvas, FontPrimary);
+    elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse Clicker");
+
+    // Ok
+    canvas_draw_icon(canvas, 63, 25, &I_Space_65x18);
+    if(model->running) {
+        canvas_set_font(canvas, FontPrimary);
+
+        FuriString* rate_label = furi_string_alloc();
+        furi_string_printf(rate_label, "%d clicks/s\n\nUp / Down", model->rate);
+        elements_multiline_text(canvas, AlignLeft, 35, furi_string_get_cstr(rate_label));
+        canvas_set_font(canvas, FontSecondary);
+        furi_string_free(rate_label);
+
+        elements_slightly_rounded_box(canvas, 66, 27, 60, 13);
+        canvas_set_color(canvas, ColorWhite);
+    } else {
+        canvas_set_font(canvas, FontPrimary);
+        elements_multiline_text(canvas, AlignLeft, 35, "Press Start\nto start\nclicking");
+        canvas_set_font(canvas, FontSecondary);
+    }
+    canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9);
+    if(model->running) {
+        elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Stop");
+    } else {
+        elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Start");
+    }
+    canvas_set_color(canvas, ColorBlack);
+
+    // Back
+    canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8);
+    elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Quit");
+}
+
+static void hid_mouse_clicker_timer_callback(void* context) {
+    furi_assert(context);
+    HidMouseClicker* hid_mouse_clicker = context;
+    with_view_model(
+        hid_mouse_clicker->view,
+        HidMouseClickerModel * model,
+        {
+            if(model->running) {
+                hid_hal_mouse_press(hid_mouse_clicker->hid, HID_MOUSE_BTN_LEFT);
+                hid_hal_mouse_release(hid_mouse_clicker->hid, HID_MOUSE_BTN_LEFT);
+            }
+        },
+        false);
+}
+
+static void hid_mouse_clicker_enter_callback(void* context) {
+    hid_mouse_clicker_start_or_restart_timer(context);
+}
+
+static void hid_mouse_clicker_exit_callback(void* context) {
+    furi_assert(context);
+    HidMouseClicker* hid_mouse_clicker = context;
+    furi_timer_stop(hid_mouse_clicker->timer);
+}
+
+static bool hid_mouse_clicker_input_callback(InputEvent* event, void* context) {
+    furi_assert(context);
+    HidMouseClicker* hid_mouse_clicker = context;
+
+    bool consumed = false;
+    bool rate_changed = false;
+
+    if(event->type != InputTypeShort && event->type != InputTypeRepeat) {
+        return false;
+    }
+
+    with_view_model(
+        hid_mouse_clicker->view,
+        HidMouseClickerModel * model,
+        {
+            switch(event->key) {
+            case InputKeyOk:
+                model->running = !model->running;
+                consumed = true;
+                break;
+            case InputKeyUp:
+                if(model->rate < MAXIMUM_CLICK_RATE) {
+                    model->rate++;
+                }
+                rate_changed = true;
+                consumed = true;
+                break;
+            case InputKeyDown:
+                if(model->rate > 1) {
+                    model->rate--;
+                }
+                rate_changed = true;
+                consumed = true;
+                break;
+            default:
+                consumed = true;
+                break;
+            }
+        },
+        true);
+
+    if(rate_changed) {
+        hid_mouse_clicker_start_or_restart_timer(context);
+    }
+
+    return consumed;
+}
+
+HidMouseClicker* hid_mouse_clicker_alloc(Hid* hid) {
+    HidMouseClicker* hid_mouse_clicker = malloc(sizeof(HidMouseClicker));
+
+    hid_mouse_clicker->view = view_alloc();
+    view_set_context(hid_mouse_clicker->view, hid_mouse_clicker);
+    view_allocate_model(
+        hid_mouse_clicker->view, ViewModelTypeLocking, sizeof(HidMouseClickerModel));
+    view_set_draw_callback(hid_mouse_clicker->view, hid_mouse_clicker_draw_callback);
+    view_set_input_callback(hid_mouse_clicker->view, hid_mouse_clicker_input_callback);
+    view_set_enter_callback(hid_mouse_clicker->view, hid_mouse_clicker_enter_callback);
+    view_set_exit_callback(hid_mouse_clicker->view, hid_mouse_clicker_exit_callback);
+
+    hid_mouse_clicker->hid = hid;
+
+    hid_mouse_clicker->timer = furi_timer_alloc(
+        hid_mouse_clicker_timer_callback, FuriTimerTypePeriodic, hid_mouse_clicker);
+
+    with_view_model(
+        hid_mouse_clicker->view,
+        HidMouseClickerModel * model,
+        {
+            model->transport = hid->transport;
+            model->rate = DEFAULT_CLICK_RATE;
+        },
+        true);
+
+    return hid_mouse_clicker;
+}
+
+void hid_mouse_clicker_free(HidMouseClicker* hid_mouse_clicker) {
+    furi_assert(hid_mouse_clicker);
+
+    furi_timer_stop(hid_mouse_clicker->timer);
+    furi_timer_free(hid_mouse_clicker->timer);
+
+    view_free(hid_mouse_clicker->view);
+
+    free(hid_mouse_clicker);
+}
+
+View* hid_mouse_clicker_get_view(HidMouseClicker* hid_mouse_clicker) {
+    furi_assert(hid_mouse_clicker);
+    return hid_mouse_clicker->view;
+}
+
+void hid_mouse_clicker_set_connected_status(HidMouseClicker* hid_mouse_clicker, bool connected) {
+    furi_assert(hid_mouse_clicker);
+    with_view_model(
+        hid_mouse_clicker->view,
+        HidMouseClickerModel * model,
+        { model->connected = connected; },
+        true);
+}

+ 14 - 0
applications/external/hid_app/views/hid_mouse_clicker.h

@@ -0,0 +1,14 @@
+#pragma once
+
+#include <gui/view.h>
+
+typedef struct Hid Hid;
+typedef struct HidMouseClicker HidMouseClicker;
+
+HidMouseClicker* hid_mouse_clicker_alloc(Hid* bt_hid);
+
+void hid_mouse_clicker_free(HidMouseClicker* hid_mouse_clicker);
+
+View* hid_mouse_clicker_get_view(HidMouseClicker* hid_mouse_clicker);
+
+void hid_mouse_clicker_set_connected_status(HidMouseClicker* hid_mouse_clicker, bool connected);

+ 1 - 1
applications/external/hid_app/views/hid_mouse_jiggler.c

@@ -95,7 +95,7 @@ static bool hid_mouse_jiggler_input_callback(InputEvent* event, void* context) {
 
     bool consumed = false;
 
-    if(event->key == InputKeyOk) {
+    if(event->type == InputTypeShort && event->key == InputKeyOk) {
         with_view_model(
             hid_mouse_jiggler->view,
             HidMouseJigglerModel * model,

+ 17 - 0
applications/external/mfkey32/application.fam

@@ -0,0 +1,17 @@
+App(
+    appid="mfkey32",
+    name="Mfkey32",
+    apptype=FlipperAppType.EXTERNAL,
+    targets=["f7"],
+    entry_point="mfkey32_main",
+    requires=[
+        "gui",
+        "storage",
+    ],
+    stack_size=1 * 1024,
+    fap_icon="mfkey.png",
+    fap_category="Nfc",
+    fap_author="noproto",
+    fap_icon_assets="images",
+    fap_weburl="https://github.com/noproto/FlipperMfkey",
+)

BIN
applications/external/mfkey32/images/mfkey.png


BIN
applications/external/mfkey32/mfkey.png


+ 1349 - 0
applications/external/mfkey32/mfkey32.c

@@ -0,0 +1,1349 @@
+#pragma GCC optimize("O3")
+#pragma GCC optimize("-funroll-all-loops")
+
+// TODO: Add keys to top of the user dictionary, not the bottom
+// TODO: More efficient dictionary bruteforce by scanning through hardcoded very common keys and previously found dictionary keys first?
+//       (a cache for napi_key_already_found_for_nonce)
+
+#include <furi.h>
+#include <furi_hal.h>
+#include "time.h"
+#include <gui/gui.h>
+#include <gui/elements.h>
+#include <input/input.h>
+#include <stdlib.h>
+#include "mfkey32_icons.h"
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <storage/storage.h>
+#include <lib/nfc/helpers/mf_classic_dict.h>
+#include <lib/toolbox/args.h>
+#include <lib/flipper_format/flipper_format.h>
+#include <dolphin/dolphin.h>
+#include <notification/notification_messages.h>
+
+#define MF_CLASSIC_DICT_FLIPPER_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc")
+#define MF_CLASSIC_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc")
+#define MF_CLASSIC_NONCE_PATH EXT_PATH("nfc/.mfkey32.log")
+#define TAG "Mfkey32"
+#define NFC_MF_CLASSIC_KEY_LEN (13)
+
+#define MIN_RAM 115632
+#define LF_POLY_ODD (0x29CE5C)
+#define LF_POLY_EVEN (0x870804)
+#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1)
+#define CONST_M2_1 (LF_POLY_ODD << 1)
+#define CONST_M1_2 (LF_POLY_ODD)
+#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1)
+#define BIT(x, n) ((x) >> (n)&1)
+#define BEBIT(x, n) BIT(x, (n) ^ 24)
+#define SWAPENDIAN(x) \
+    ((x) = ((x) >> 8 & 0xff00ff) | ((x)&0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16)
+//#define SIZEOF(arr) sizeof(arr) / sizeof(*arr)
+
+static int eta_round_time = 56;
+static int eta_total_time = 900;
+// MSB_LIMIT: Chunk size (out of 256)
+static int MSB_LIMIT = 16;
+
+struct Crypto1State {
+    uint32_t odd, even;
+};
+struct Crypto1Params {
+    uint64_t key;
+    uint32_t nr0_enc, uid_xor_nt0, uid_xor_nt1, nr1_enc, p64b, ar1_enc;
+};
+struct Msb {
+    int tail;
+    uint32_t states[768];
+};
+
+typedef enum {
+    EventTypeTick,
+    EventTypeKey,
+} EventType;
+
+typedef struct {
+    EventType type;
+    InputEvent input;
+} PluginEvent;
+
+typedef enum {
+    MissingNonces,
+    ZeroNonces,
+} MfkeyError;
+
+typedef enum {
+    Ready,
+    Initializing,
+    DictionaryAttack,
+    MfkeyAttack,
+    Complete,
+    Error,
+    Help,
+} MfkeyState;
+
+// TODO: Can we eliminate any of the members of this struct?
+typedef struct {
+    FuriMutex* mutex;
+    MfkeyError err;
+    MfkeyState mfkey_state;
+    int cracked;
+    int unique_cracked;
+    int num_completed;
+    int total;
+    int dict_count;
+    int search;
+    int eta_timestamp;
+    int eta_total;
+    int eta_round;
+    bool is_thread_running;
+    bool close_thread_please;
+    FuriThread* mfkeythread;
+} ProgramState;
+
+// TODO: Merge this with Crypto1Params?
+typedef struct {
+    uint32_t uid; // serial number
+    uint32_t nt0; // tag challenge first
+    uint32_t nt1; // tag challenge second
+    uint32_t nr0_enc; // first encrypted reader challenge
+    uint32_t ar0_enc; // first encrypted reader response
+    uint32_t nr1_enc; // second encrypted reader challenge
+    uint32_t ar1_enc; // second encrypted reader response
+} MfClassicNonce;
+
+typedef struct {
+    Stream* stream;
+    uint32_t total_nonces;
+    MfClassicNonce* remaining_nonce_array;
+    size_t remaining_nonces;
+} MfClassicNonceArray;
+
+struct MfClassicDict {
+    Stream* stream;
+    uint32_t total_keys;
+};
+
+static const uint8_t table[256] = {
+    0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3,
+    4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4,
+    4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4,
+    5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5,
+    4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2,
+    3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5,
+    5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4,
+    5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6,
+    4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8};
+static const uint8_t lookup1[256] = {
+    0, 0,  16, 16, 0,  16, 0,  0,  0, 16, 0,  0,  16, 16, 16, 16, 0, 0,  16, 16, 0,  16, 0,  0,
+    0, 16, 0,  0,  16, 16, 16, 16, 0, 0,  16, 16, 0,  16, 0,  0,  0, 16, 0,  0,  16, 16, 16, 16,
+    8, 8,  24, 24, 8,  24, 8,  8,  8, 24, 8,  8,  24, 24, 24, 24, 8, 8,  24, 24, 8,  24, 8,  8,
+    8, 24, 8,  8,  24, 24, 24, 24, 8, 8,  24, 24, 8,  24, 8,  8,  8, 24, 8,  8,  24, 24, 24, 24,
+    0, 0,  16, 16, 0,  16, 0,  0,  0, 16, 0,  0,  16, 16, 16, 16, 0, 0,  16, 16, 0,  16, 0,  0,
+    0, 16, 0,  0,  16, 16, 16, 16, 8, 8,  24, 24, 8,  24, 8,  8,  8, 24, 8,  8,  24, 24, 24, 24,
+    0, 0,  16, 16, 0,  16, 0,  0,  0, 16, 0,  0,  16, 16, 16, 16, 0, 0,  16, 16, 0,  16, 0,  0,
+    0, 16, 0,  0,  16, 16, 16, 16, 8, 8,  24, 24, 8,  24, 8,  8,  8, 24, 8,  8,  24, 24, 24, 24,
+    8, 8,  24, 24, 8,  24, 8,  8,  8, 24, 8,  8,  24, 24, 24, 24, 0, 0,  16, 16, 0,  16, 0,  0,
+    0, 16, 0,  0,  16, 16, 16, 16, 8, 8,  24, 24, 8,  24, 8,  8,  8, 24, 8,  8,  24, 24, 24, 24,
+    8, 8,  24, 24, 8,  24, 8,  8,  8, 24, 8,  8,  24, 24, 24, 24};
+static const uint8_t lookup2[256] = {
+    0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4,
+    4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6,
+    2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2,
+    2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4,
+    0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2,
+    2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4,
+    4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2,
+    2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2,
+    2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6};
+
+uint32_t prng_successor(uint32_t x, uint32_t n) {
+    SWAPENDIAN(x);
+    while(n--) x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31;
+    return SWAPENDIAN(x);
+}
+
+static inline int filter(uint32_t const x) {
+    uint32_t f;
+    f = lookup1[x & 0xff] | lookup2[(x >> 8) & 0xff];
+    f |= 0x0d938 >> (x >> 16 & 0xf) & 1;
+    return BIT(0xEC57E80A, f);
+}
+
+static inline uint8_t evenparity32(uint32_t x) {
+    if((table[x & 0xff] + table[(x >> 8) & 0xff] + table[(x >> 16) & 0xff] + table[x >> 24]) % 2 ==
+       0) {
+        return 0;
+    } else {
+        return 1;
+    }
+    //return ((table[x & 0xff] + table[(x >> 8) & 0xff] + table[(x >> 16) & 0xff] + table[x >> 24]) % 2) & 0xFF;
+}
+
+static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2) {
+    int p = data[item] >> 25;
+    p = p << 1 | evenparity32(data[item] & mask1);
+    p = p << 1 | evenparity32(data[item] & mask2);
+    data[item] = p << 24 | (data[item] & 0xffffff);
+}
+
+void crypto1_get_lfsr(struct Crypto1State* state, uint64_t* lfsr) {
+    int i;
+    for(*lfsr = 0, i = 23; i >= 0; --i) {
+        *lfsr = *lfsr << 1 | BIT(state->odd, i ^ 3);
+        *lfsr = *lfsr << 1 | BIT(state->even, i ^ 3);
+    }
+}
+
+static inline uint32_t crypt_word(struct Crypto1State* s) {
+    // "in" and "x" are always 0 (last iteration)
+    uint32_t res_ret = 0;
+    uint32_t feedin, t;
+    for(int i = 0; i <= 31; i++) {
+        res_ret |= (filter(s->odd) << (24 ^ i)); //-V629
+        feedin = LF_POLY_EVEN & s->even;
+        feedin ^= LF_POLY_ODD & s->odd;
+        s->even = s->even << 1 | (evenparity32(feedin));
+        t = s->odd, s->odd = s->even, s->even = t;
+    }
+    return res_ret;
+}
+
+static inline void crypt_word_noret(struct Crypto1State* s, uint32_t in, int x) {
+    uint8_t ret;
+    uint32_t feedin, t, next_in;
+    for(int i = 0; i <= 31; i++) {
+        next_in = BEBIT(in, i);
+        ret = filter(s->odd);
+        feedin = ret & (!!x);
+        feedin ^= LF_POLY_EVEN & s->even;
+        feedin ^= LF_POLY_ODD & s->odd;
+        feedin ^= !!next_in;
+        s->even = s->even << 1 | (evenparity32(feedin));
+        t = s->odd, s->odd = s->even, s->even = t;
+    }
+    return;
+}
+
+static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int x) {
+    uint8_t ret;
+    uint32_t feedin, t, next_in;
+    for(int i = 31; i >= 0; i--) {
+        next_in = BEBIT(in, i);
+        s->odd &= 0xffffff;
+        t = s->odd, s->odd = s->even, s->even = t;
+        ret = filter(s->odd);
+        feedin = ret & (!!x);
+        feedin ^= s->even & 1;
+        feedin ^= LF_POLY_EVEN & (s->even >>= 1);
+        feedin ^= LF_POLY_ODD & s->odd;
+        feedin ^= !!next_in;
+        s->even |= (evenparity32(feedin)) << 23;
+    }
+    return;
+}
+
+int key_already_found_for_nonce(
+    uint64_t* keyarray,
+    int keyarray_size,
+    uint32_t uid_xor_nt1,
+    uint32_t nr1_enc,
+    uint32_t p64b,
+    uint32_t ar1_enc) {
+    for(int k = 0; k < keyarray_size; k++) {
+        struct Crypto1State temp = {0, 0};
+
+        for(int i = 0; i < 24; i++) {
+            (&temp)->odd |= (BIT(keyarray[k], 2 * i + 1) << (i ^ 3));
+            (&temp)->even |= (BIT(keyarray[k], 2 * i) << (i ^ 3));
+        }
+
+        crypt_word_noret(&temp, uid_xor_nt1, 0);
+        crypt_word_noret(&temp, nr1_enc, 1);
+
+        if(ar1_enc == (crypt_word(&temp) ^ p64b)) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+int check_state(struct Crypto1State* t, struct Crypto1Params* p) {
+    if(!(t->odd | t->even)) return 0;
+    rollback_word_noret(t, 0, 0);
+    rollback_word_noret(t, p->nr0_enc, 1);
+    rollback_word_noret(t, p->uid_xor_nt0, 0);
+    struct Crypto1State temp = {t->odd, t->even};
+    crypt_word_noret(t, p->uid_xor_nt1, 0);
+    crypt_word_noret(t, p->nr1_enc, 1);
+    if(p->ar1_enc == (crypt_word(t) ^ p->p64b)) {
+        crypto1_get_lfsr(&temp, &(p->key));
+        return 1;
+    }
+    return 0;
+}
+
+static inline int state_loop(unsigned int* states_buffer, int xks, int m1, int m2) {
+    int states_tail = 0;
+    int round = 0, s = 0, xks_bit = 0;
+
+    for(round = 1; round <= 12; round++) {
+        xks_bit = BIT(xks, round);
+
+        for(s = 0; s <= states_tail; s++) {
+            states_buffer[s] <<= 1;
+
+            if((filter(states_buffer[s]) ^ filter(states_buffer[s] | 1)) != 0) {
+                states_buffer[s] |= filter(states_buffer[s]) ^ xks_bit;
+                if(round > 4) {
+                    update_contribution(states_buffer, s, m1, m2);
+                }
+            } else if(filter(states_buffer[s]) == xks_bit) {
+                // TODO: Refactor
+                if(round > 4) {
+                    states_buffer[++states_tail] = states_buffer[s + 1];
+                    states_buffer[s + 1] = states_buffer[s] | 1;
+                    update_contribution(states_buffer, s, m1, m2);
+                    s++;
+                    update_contribution(states_buffer, s, m1, m2);
+                } else {
+                    states_buffer[++states_tail] = states_buffer[++s];
+                    states_buffer[s] = states_buffer[s - 1] | 1;
+                }
+            } else {
+                states_buffer[s--] = states_buffer[states_tail--];
+            }
+        }
+    }
+
+    return states_tail;
+}
+
+int binsearch(unsigned int data[], int start, int stop) {
+    int mid, val = data[stop] & 0xff000000;
+    while(start != stop) {
+        mid = (stop - start) >> 1;
+        if((data[start + mid] ^ 0x80000000) > (val ^ 0x80000000))
+            stop = start + mid;
+        else
+            start += mid + 1;
+    }
+    return start;
+}
+void quicksort(unsigned int array[], int low, int high) {
+    //if (SIZEOF(array) == 0)
+    //    return;
+    if(low >= high) return;
+    int middle = low + (high - low) / 2;
+    unsigned int pivot = array[middle];
+    int i = low, j = high;
+    while(i <= j) {
+        while(array[i] < pivot) {
+            i++;
+        }
+        while(array[j] > pivot) {
+            j--;
+        }
+        if(i <= j) { // swap
+            int temp = array[i];
+            array[i] = array[j];
+            array[j] = temp;
+            i++;
+            j--;
+        }
+    }
+    if(low < j) {
+        quicksort(array, low, j);
+    }
+    if(high > i) {
+        quicksort(array, i, high);
+    }
+}
+int extend_table(unsigned int data[], int tbl, int end, int bit, int m1, int m2) {
+    for(data[tbl] <<= 1; tbl <= end; data[++tbl] <<= 1) {
+        if((filter(data[tbl]) ^ filter(data[tbl] | 1)) != 0) {
+            data[tbl] |= filter(data[tbl]) ^ bit;
+            update_contribution(data, tbl, m1, m2);
+        } else if(filter(data[tbl]) == bit) {
+            data[++end] = data[tbl + 1];
+            data[tbl + 1] = data[tbl] | 1;
+            update_contribution(data, tbl, m1, m2);
+            tbl++;
+            update_contribution(data, tbl, m1, m2);
+        } else {
+            data[tbl--] = data[end--];
+        }
+    }
+    return end;
+}
+
+int old_recover(
+    unsigned int odd[],
+    int o_head,
+    int o_tail,
+    int oks,
+    unsigned int even[],
+    int e_head,
+    int e_tail,
+    int eks,
+    int rem,
+    int s,
+    struct Crypto1Params* p,
+    int first_run) {
+    int o, e, i;
+    if(rem == -1) {
+        for(e = e_head; e <= e_tail; ++e) {
+            even[e] = (even[e] << 1) ^ evenparity32(even[e] & LF_POLY_EVEN);
+            for(o = o_head; o <= o_tail; ++o, ++s) {
+                struct Crypto1State temp = {0, 0};
+                temp.even = odd[o];
+                temp.odd = even[e] ^ evenparity32(odd[o] & LF_POLY_ODD);
+                if(check_state(&temp, p)) {
+                    return -1;
+                }
+            }
+        }
+        return s;
+    }
+    if(first_run == 0) {
+        for(i = 0; (i < 4) && (rem-- != 0); i++) {
+            oks >>= 1;
+            eks >>= 1;
+            o_tail = extend_table(
+                odd, o_head, o_tail, oks & 1, LF_POLY_EVEN << 1 | 1, LF_POLY_ODD << 1);
+            if(o_head > o_tail) return s;
+            e_tail =
+                extend_table(even, e_head, e_tail, eks & 1, LF_POLY_ODD, LF_POLY_EVEN << 1 | 1);
+            if(e_head > e_tail) return s;
+        }
+    }
+    first_run = 0;
+    quicksort(odd, o_head, o_tail);
+    quicksort(even, e_head, e_tail);
+    while(o_tail >= o_head && e_tail >= e_head) {
+        if(((odd[o_tail] ^ even[e_tail]) >> 24) == 0) {
+            o_tail = binsearch(odd, o_head, o = o_tail);
+            e_tail = binsearch(even, e_head, e = e_tail);
+            s = old_recover(odd, o_tail--, o, oks, even, e_tail--, e, eks, rem, s, p, first_run);
+            if(s == -1) {
+                break;
+            }
+        } else if((odd[o_tail] ^ 0x80000000) > (even[e_tail] ^ 0x80000000)) {
+            o_tail = binsearch(odd, o_head, o_tail) - 1;
+        } else {
+            e_tail = binsearch(even, e_head, e_tail) - 1;
+        }
+    }
+    return s;
+}
+
+static inline int sync_state(ProgramState* program_state) {
+    int ts = furi_hal_rtc_get_timestamp();
+    program_state->eta_round = program_state->eta_round - (ts - program_state->eta_timestamp);
+    program_state->eta_total = program_state->eta_total - (ts - program_state->eta_timestamp);
+    program_state->eta_timestamp = ts;
+    if(program_state->close_thread_please) {
+        return 1;
+    }
+    return 0;
+}
+
+int calculate_msb_tables(
+    int oks,
+    int eks,
+    int msb_round,
+    struct Crypto1Params* p,
+    unsigned int* states_buffer,
+    struct Msb* odd_msbs,
+    struct Msb* even_msbs,
+    unsigned int* temp_states_odd,
+    unsigned int* temp_states_even,
+    ProgramState* program_state) {
+    //FURI_LOG_I(TAG, "MSB GO %i", msb_iter); // DEBUG
+    unsigned int msb_head = (MSB_LIMIT * msb_round); // msb_iter ranges from 0 to (256/MSB_LIMIT)-1
+    unsigned int msb_tail = (MSB_LIMIT * (msb_round + 1));
+    int states_tail = 0, tail = 0;
+    int i = 0, j = 0, semi_state = 0, found = 0;
+    unsigned int msb = 0;
+    // TODO: Why is this necessary?
+    memset(odd_msbs, 0, MSB_LIMIT * sizeof(struct Msb));
+    memset(even_msbs, 0, MSB_LIMIT * sizeof(struct Msb));
+
+    for(semi_state = 1 << 20; semi_state >= 0; semi_state--) {
+        if(semi_state % 32768 == 0) {
+            if(sync_state(program_state) == 1) {
+                return 0;
+            }
+        }
+
+        if(filter(semi_state) == (oks & 1)) { //-V547
+            states_buffer[0] = semi_state;
+            states_tail = state_loop(states_buffer, oks, CONST_M1_1, CONST_M2_1);
+
+            for(i = states_tail; i >= 0; i--) {
+                msb = states_buffer[i] >> 24;
+                if((msb >= msb_head) && (msb < msb_tail)) {
+                    found = 0;
+                    for(j = 0; j < odd_msbs[msb - msb_head].tail - 1; j++) {
+                        if(odd_msbs[msb - msb_head].states[j] == states_buffer[i]) {
+                            found = 1;
+                            break;
+                        }
+                    }
+
+                    if(!found) {
+                        tail = odd_msbs[msb - msb_head].tail++;
+                        odd_msbs[msb - msb_head].states[tail] = states_buffer[i];
+                    }
+                }
+            }
+        }
+
+        if(filter(semi_state) == (eks & 1)) { //-V547
+            states_buffer[0] = semi_state;
+            states_tail = state_loop(states_buffer, eks, CONST_M1_2, CONST_M2_2);
+
+            for(i = 0; i <= states_tail; i++) {
+                msb = states_buffer[i] >> 24;
+                if((msb >= msb_head) && (msb < msb_tail)) {
+                    found = 0;
+
+                    for(j = 0; j < even_msbs[msb - msb_head].tail; j++) {
+                        if(even_msbs[msb - msb_head].states[j] == states_buffer[i]) {
+                            found = 1;
+                            break;
+                        }
+                    }
+
+                    if(!found) {
+                        tail = even_msbs[msb - msb_head].tail++;
+                        even_msbs[msb - msb_head].states[tail] = states_buffer[i];
+                    }
+                }
+            }
+        }
+    }
+
+    oks >>= 12;
+    eks >>= 12;
+
+    for(i = 0; i < MSB_LIMIT; i++) {
+        if(sync_state(program_state) == 1) {
+            return 0;
+        }
+        // TODO: Why is this necessary?
+        memset(temp_states_even, 0, sizeof(unsigned int) * (1280));
+        memset(temp_states_odd, 0, sizeof(unsigned int) * (1280));
+        memcpy(temp_states_odd, odd_msbs[i].states, odd_msbs[i].tail * sizeof(unsigned int));
+        memcpy(temp_states_even, even_msbs[i].states, even_msbs[i].tail * sizeof(unsigned int));
+        int res = old_recover(
+            temp_states_odd,
+            0,
+            odd_msbs[i].tail,
+            oks,
+            temp_states_even,
+            0,
+            even_msbs[i].tail,
+            eks,
+            3,
+            0,
+            p,
+            1);
+        if(res == -1) {
+            return 1;
+        }
+        //odd_msbs[i].tail = 0;
+        //even_msbs[i].tail = 0;
+    }
+
+    return 0;
+}
+
+bool recover(struct Crypto1Params* p, int ks2, ProgramState* program_state) {
+    bool found = false;
+    unsigned int* states_buffer = malloc(sizeof(unsigned int) * (2 << 9));
+    struct Msb* odd_msbs = (struct Msb*)malloc(MSB_LIMIT * sizeof(struct Msb));
+    struct Msb* even_msbs = (struct Msb*)malloc(MSB_LIMIT * sizeof(struct Msb));
+    unsigned int* temp_states_odd = malloc(sizeof(unsigned int) * (1280));
+    unsigned int* temp_states_even = malloc(sizeof(unsigned int) * (1280));
+    int oks = 0, eks = 0;
+    int i = 0, msb = 0;
+    for(i = 31; i >= 0; i -= 2) {
+        oks = oks << 1 | BEBIT(ks2, i);
+    }
+    for(i = 30; i >= 0; i -= 2) {
+        eks = eks << 1 | BEBIT(ks2, i);
+    }
+    int bench_start = furi_hal_rtc_get_timestamp();
+    program_state->eta_total = eta_total_time;
+    program_state->eta_timestamp = bench_start;
+    for(msb = 0; msb <= ((256 / MSB_LIMIT) - 1); msb++) {
+        program_state->search = msb;
+        program_state->eta_round = eta_round_time;
+        program_state->eta_total = eta_total_time - (eta_round_time * msb);
+        if(calculate_msb_tables(
+               oks,
+               eks,
+               msb,
+               p,
+               states_buffer,
+               odd_msbs,
+               even_msbs,
+               temp_states_odd,
+               temp_states_even,
+               program_state)) {
+            int bench_stop = furi_hal_rtc_get_timestamp();
+            FURI_LOG_I(TAG, "Cracked in %i seconds", bench_stop - bench_start);
+            found = true;
+            break;
+        }
+        if(program_state->close_thread_please) {
+            break;
+        }
+    }
+    free(states_buffer);
+    free(odd_msbs);
+    free(even_msbs);
+    free(temp_states_odd);
+    free(temp_states_even);
+    return found;
+}
+
+bool napi_mf_classic_dict_check_presence(MfClassicDictType dict_type) {
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+
+    bool dict_present = false;
+    if(dict_type == MfClassicDictTypeSystem) {
+        dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_FLIPPER_PATH, NULL) == FSE_OK;
+    } else if(dict_type == MfClassicDictTypeUser) {
+        dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_USER_PATH, NULL) == FSE_OK;
+    }
+
+    furi_record_close(RECORD_STORAGE);
+
+    return dict_present;
+}
+
+MfClassicDict* napi_mf_classic_dict_alloc(MfClassicDictType dict_type) {
+    MfClassicDict* dict = malloc(sizeof(MfClassicDict));
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    dict->stream = buffered_file_stream_alloc(storage);
+    furi_record_close(RECORD_STORAGE);
+
+    bool dict_loaded = false;
+    do {
+        if(dict_type == MfClassicDictTypeSystem) {
+            if(!buffered_file_stream_open(
+                   dict->stream,
+                   MF_CLASSIC_DICT_FLIPPER_PATH,
+                   FSAM_READ_WRITE,
+                   FSOM_OPEN_EXISTING)) {
+                buffered_file_stream_close(dict->stream);
+                break;
+            }
+        } else if(dict_type == MfClassicDictTypeUser) {
+            if(!buffered_file_stream_open(
+                   dict->stream, MF_CLASSIC_DICT_USER_PATH, FSAM_READ_WRITE, FSOM_OPEN_ALWAYS)) {
+                buffered_file_stream_close(dict->stream);
+                break;
+            }
+        }
+
+        // Check for newline ending
+        if(!stream_eof(dict->stream)) {
+            if(!stream_seek(dict->stream, -1, StreamOffsetFromEnd)) break;
+            uint8_t last_char = 0;
+            if(stream_read(dict->stream, &last_char, 1) != 1) break;
+            if(last_char != '\n') {
+                FURI_LOG_D(TAG, "Adding new line ending");
+                if(stream_write_char(dict->stream, '\n') != 1) break;
+            }
+            if(!stream_rewind(dict->stream)) break;
+        }
+
+        // Read total amount of keys
+        FuriString* next_line;
+        next_line = furi_string_alloc();
+        while(true) {
+            if(!stream_read_line(dict->stream, next_line)) {
+                FURI_LOG_T(TAG, "No keys left in dict");
+                break;
+            }
+            FURI_LOG_T(
+                TAG,
+                "Read line: %s, len: %zu",
+                furi_string_get_cstr(next_line),
+                furi_string_size(next_line));
+            if(furi_string_get_char(next_line, 0) == '#') continue;
+            if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue;
+            dict->total_keys++;
+        }
+        furi_string_free(next_line);
+        stream_rewind(dict->stream);
+
+        dict_loaded = true;
+        FURI_LOG_I(TAG, "Loaded dictionary with %lu keys", dict->total_keys);
+    } while(false);
+
+    if(!dict_loaded) {
+        buffered_file_stream_close(dict->stream);
+        free(dict);
+        dict = NULL;
+    }
+
+    return dict;
+}
+
+bool napi_mf_classic_dict_add_key_str(MfClassicDict* dict, FuriString* key) {
+    furi_assert(dict);
+    furi_assert(dict->stream);
+    FURI_LOG_I(TAG, "Saving key: %s", furi_string_get_cstr(key));
+
+    furi_string_cat_printf(key, "\n");
+
+    bool key_added = false;
+    do {
+        if(!stream_seek(dict->stream, 0, StreamOffsetFromEnd)) break;
+        if(!stream_insert_string(dict->stream, key)) break;
+        dict->total_keys++;
+        key_added = true;
+    } while(false);
+
+    furi_string_left(key, 12);
+    return key_added;
+}
+
+void napi_mf_classic_dict_free(MfClassicDict* dict) {
+    furi_assert(dict);
+    furi_assert(dict->stream);
+
+    buffered_file_stream_close(dict->stream);
+    stream_free(dict->stream);
+    free(dict);
+}
+
+static void napi_mf_classic_dict_int_to_str(uint8_t* key_int, FuriString* key_str) {
+    furi_string_reset(key_str);
+    for(size_t i = 0; i < 6; i++) {
+        furi_string_cat_printf(key_str, "%02X", key_int[i]);
+    }
+}
+
+static void napi_mf_classic_dict_str_to_int(FuriString* key_str, uint64_t* key_int) {
+    uint8_t key_byte_tmp;
+
+    *key_int = 0ULL;
+    for(uint8_t i = 0; i < 12; i += 2) {
+        args_char_to_hex(
+            furi_string_get_char(key_str, i), furi_string_get_char(key_str, i + 1), &key_byte_tmp);
+        *key_int |= (uint64_t)key_byte_tmp << (8 * (5 - i / 2));
+    }
+}
+
+uint32_t napi_mf_classic_dict_get_total_keys(MfClassicDict* dict) {
+    furi_assert(dict);
+
+    return dict->total_keys;
+}
+
+bool napi_mf_classic_dict_rewind(MfClassicDict* dict) {
+    furi_assert(dict);
+    furi_assert(dict->stream);
+
+    return stream_rewind(dict->stream);
+}
+
+bool napi_mf_classic_dict_get_next_key_str(MfClassicDict* dict, FuriString* key) {
+    furi_assert(dict);
+    furi_assert(dict->stream);
+
+    bool key_read = false;
+    furi_string_reset(key);
+    while(!key_read) {
+        if(!stream_read_line(dict->stream, key)) break;
+        if(furi_string_get_char(key, 0) == '#') continue;
+        if(furi_string_size(key) != NFC_MF_CLASSIC_KEY_LEN) continue;
+        furi_string_left(key, 12);
+        key_read = true;
+    }
+
+    return key_read;
+}
+
+bool napi_mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key) {
+    furi_assert(dict);
+    furi_assert(dict->stream);
+
+    FuriString* temp_key;
+    temp_key = furi_string_alloc();
+    bool key_read = napi_mf_classic_dict_get_next_key_str(dict, temp_key);
+    if(key_read) {
+        napi_mf_classic_dict_str_to_int(temp_key, key);
+    }
+    furi_string_free(temp_key);
+    return key_read;
+}
+
+bool napi_mf_classic_dict_is_key_present_str(MfClassicDict* dict, FuriString* key) {
+    furi_assert(dict);
+    furi_assert(dict->stream);
+
+    FuriString* next_line;
+    next_line = furi_string_alloc();
+
+    bool key_found = false;
+    stream_rewind(dict->stream);
+    while(!key_found) { //-V654
+        if(!stream_read_line(dict->stream, next_line)) break;
+        if(furi_string_get_char(next_line, 0) == '#') continue;
+        if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue;
+        furi_string_left(next_line, 12);
+        if(!furi_string_equal(key, next_line)) continue;
+        key_found = true;
+    }
+
+    furi_string_free(next_line);
+    return key_found;
+}
+
+bool napi_mf_classic_dict_is_key_present(MfClassicDict* dict, uint8_t* key) {
+    FuriString* temp_key;
+
+    temp_key = furi_string_alloc();
+    napi_mf_classic_dict_int_to_str(key, temp_key);
+    bool key_found = napi_mf_classic_dict_is_key_present_str(dict, temp_key);
+    furi_string_free(temp_key);
+    return key_found;
+}
+
+bool napi_key_already_found_for_nonce(
+    MfClassicDict* dict,
+    uint32_t uid_xor_nt1,
+    uint32_t nr1_enc,
+    uint32_t p64b,
+    uint32_t ar1_enc) {
+    bool found = false;
+    uint64_t k = 0;
+    napi_mf_classic_dict_rewind(dict);
+    while(napi_mf_classic_dict_get_next_key(dict, &k)) {
+        struct Crypto1State temp = {0, 0};
+        int i;
+        for(i = 0; i < 24; i++) {
+            (&temp)->odd |= (BIT(k, 2 * i + 1) << (i ^ 3));
+            (&temp)->even |= (BIT(k, 2 * i) << (i ^ 3));
+        }
+        crypt_word_noret(&temp, uid_xor_nt1, 0);
+        crypt_word_noret(&temp, nr1_enc, 1);
+        if(ar1_enc == (crypt_word(&temp) ^ p64b)) {
+            found = true;
+            break;
+        }
+    }
+    return found;
+}
+
+bool napi_mf_classic_nonces_check_presence() {
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+
+    bool nonces_present = storage_common_stat(storage, MF_CLASSIC_NONCE_PATH, NULL) == FSE_OK;
+
+    furi_record_close(RECORD_STORAGE);
+
+    return nonces_present;
+}
+
+MfClassicNonceArray* napi_mf_classic_nonce_array_alloc(
+    MfClassicDict* system_dict,
+    bool system_dict_exists,
+    MfClassicDict* user_dict,
+    ProgramState* program_state) {
+    MfClassicNonceArray* nonce_array = malloc(sizeof(MfClassicNonceArray));
+    MfClassicNonce* remaining_nonce_array_init = malloc(sizeof(MfClassicNonce) * 1);
+    nonce_array->remaining_nonce_array = remaining_nonce_array_init;
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    nonce_array->stream = buffered_file_stream_alloc(storage);
+    furi_record_close(RECORD_STORAGE);
+
+    bool array_loaded = false;
+    do {
+        // https://github.com/flipperdevices/flipperzero-firmware/blob/5134f44c09d39344a8747655c0d59864bb574b96/applications/services/storage/filesystem_api_defines.h#L8-L22
+        if(!buffered_file_stream_open(
+               nonce_array->stream, MF_CLASSIC_NONCE_PATH, FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) {
+            buffered_file_stream_close(nonce_array->stream);
+            break;
+        }
+
+        // Check for newline ending
+        if(!stream_eof(nonce_array->stream)) {
+            if(!stream_seek(nonce_array->stream, -1, StreamOffsetFromEnd)) break;
+            uint8_t last_char = 0;
+            if(stream_read(nonce_array->stream, &last_char, 1) != 1) break;
+            if(last_char != '\n') {
+                FURI_LOG_D(TAG, "Adding new line ending");
+                if(stream_write_char(nonce_array->stream, '\n') != 1) break;
+            }
+            if(!stream_rewind(nonce_array->stream)) break;
+        }
+
+        // Read total amount of nonces
+        FuriString* next_line;
+        next_line = furi_string_alloc();
+        while(!(program_state->close_thread_please)) {
+            if(!stream_read_line(nonce_array->stream, next_line)) {
+                FURI_LOG_T(TAG, "No nonces left");
+                break;
+            }
+            FURI_LOG_T(
+                TAG,
+                "Read line: %s, len: %zu",
+                furi_string_get_cstr(next_line),
+                furi_string_size(next_line));
+            if(!furi_string_start_with_str(next_line, "Sec")) continue;
+            const char* next_line_cstr = furi_string_get_cstr(next_line);
+            MfClassicNonce res = {0};
+            int i = 0;
+            char* endptr;
+            for(i = 0; i <= 17; i++) {
+                if(i != 0) {
+                    next_line_cstr = strchr(next_line_cstr, ' ');
+                    if(next_line_cstr) {
+                        next_line_cstr++;
+                    } else {
+                        break;
+                    }
+                }
+                unsigned long value = strtoul(next_line_cstr, &endptr, 16);
+                switch(i) {
+                case 5:
+                    res.uid = value;
+                    break;
+                case 7:
+                    res.nt0 = value;
+                    break;
+                case 9:
+                    res.nr0_enc = value;
+                    break;
+                case 11:
+                    res.ar0_enc = value;
+                    break;
+                case 13:
+                    res.nt1 = value;
+                    break;
+                case 15:
+                    res.nr1_enc = value;
+                    break;
+                case 17:
+                    res.ar1_enc = value;
+                    break;
+                default:
+                    break; // Do nothing
+                }
+                next_line_cstr = endptr;
+            }
+            (program_state->total)++;
+            uint32_t p64b = prng_successor(res.nt1, 64);
+            if((system_dict_exists &&
+                napi_key_already_found_for_nonce(
+                    system_dict, res.uid ^ res.nt1, res.nr1_enc, p64b, res.ar1_enc)) ||
+               (napi_key_already_found_for_nonce(
+                   user_dict, res.uid ^ res.nt1, res.nr1_enc, p64b, res.ar1_enc))) {
+                (program_state->cracked)++;
+                (program_state->num_completed)++;
+                continue;
+            }
+            FURI_LOG_I(TAG, "No key found for %8lx %8lx", res.uid, res.ar1_enc);
+            // TODO: Refactor
+            nonce_array->remaining_nonce_array = realloc( //-V701
+                nonce_array->remaining_nonce_array,
+                sizeof(MfClassicNonce) * ((nonce_array->remaining_nonces) + 1));
+            nonce_array->remaining_nonces++;
+            nonce_array->remaining_nonce_array[(nonce_array->remaining_nonces) - 1] = res;
+            nonce_array->total_nonces++;
+        }
+        furi_string_free(next_line);
+        buffered_file_stream_close(nonce_array->stream);
+
+        array_loaded = true;
+        FURI_LOG_I(TAG, "Loaded %lu nonces", nonce_array->total_nonces);
+    } while(false);
+
+    if(!array_loaded) {
+        free(nonce_array);
+        nonce_array = NULL;
+    }
+
+    return nonce_array;
+}
+
+void napi_mf_classic_nonce_array_free(MfClassicNonceArray* nonce_array) {
+    furi_assert(nonce_array);
+    furi_assert(nonce_array->stream);
+
+    buffered_file_stream_close(nonce_array->stream);
+    stream_free(nonce_array->stream);
+    free(nonce_array);
+}
+
+static void finished_beep() {
+    // Beep to indicate completion
+    NotificationApp* notification = furi_record_open("notification");
+    notification_message(notification, &sequence_audiovisual_alert);
+    notification_message(notification, &sequence_display_backlight_on);
+    furi_record_close("notification");
+}
+
+void mfkey32(ProgramState* program_state) {
+    uint64_t found_key; // recovered key
+    size_t keyarray_size = 0;
+    uint64_t* keyarray = malloc(sizeof(uint64_t) * 1);
+    uint32_t i = 0, j = 0;
+    // Check for nonces
+    if(!napi_mf_classic_nonces_check_presence()) {
+        program_state->err = MissingNonces;
+        program_state->mfkey_state = Error;
+        free(keyarray);
+        return;
+    }
+    // Read dictionaries (optional)
+    MfClassicDict* system_dict = {0};
+    bool system_dict_exists = napi_mf_classic_dict_check_presence(MfClassicDictTypeSystem);
+    MfClassicDict* user_dict = {0};
+    bool user_dict_exists = napi_mf_classic_dict_check_presence(MfClassicDictTypeUser);
+    uint32_t total_dict_keys = 0;
+    if(system_dict_exists) {
+        system_dict = napi_mf_classic_dict_alloc(MfClassicDictTypeSystem);
+        total_dict_keys += napi_mf_classic_dict_get_total_keys(system_dict);
+    }
+    user_dict = napi_mf_classic_dict_alloc(MfClassicDictTypeUser);
+    if(user_dict_exists) {
+        total_dict_keys += napi_mf_classic_dict_get_total_keys(user_dict);
+    }
+    user_dict_exists = true;
+    program_state->dict_count = total_dict_keys;
+    program_state->mfkey_state = DictionaryAttack;
+    // Read nonces
+    MfClassicNonceArray* nonce_arr;
+    nonce_arr = napi_mf_classic_nonce_array_alloc(
+        system_dict, system_dict_exists, user_dict, program_state);
+    if(system_dict_exists) {
+        napi_mf_classic_dict_free(system_dict);
+    }
+    if(nonce_arr->total_nonces == 0) {
+        // Nothing to crack
+        program_state->err = ZeroNonces;
+        program_state->mfkey_state = Error;
+        napi_mf_classic_nonce_array_free(nonce_arr);
+        napi_mf_classic_dict_free(user_dict);
+        free(keyarray);
+        return;
+    }
+    if(memmgr_get_free_heap() < MIN_RAM) {
+        // System has less than the guaranteed amount of RAM (140 KB) - adjust some parameters to run anyway at half speed
+        eta_round_time *= 2;
+        eta_total_time *= 2;
+        MSB_LIMIT /= 2;
+    }
+    program_state->mfkey_state = MfkeyAttack;
+    // TODO: Work backwards on this array and free memory
+    for(i = 0; i < nonce_arr->total_nonces; i++) {
+        MfClassicNonce next_nonce = nonce_arr->remaining_nonce_array[i];
+        uint32_t p64 = prng_successor(next_nonce.nt0, 64);
+        uint32_t p64b = prng_successor(next_nonce.nt1, 64);
+        if(key_already_found_for_nonce(
+               keyarray,
+               keyarray_size,
+               next_nonce.uid ^ next_nonce.nt1,
+               next_nonce.nr1_enc,
+               p64b,
+               next_nonce.ar1_enc)) {
+            nonce_arr->remaining_nonces--;
+            (program_state->cracked)++;
+            (program_state->num_completed)++;
+            continue;
+        }
+        FURI_LOG_I(TAG, "Cracking %8lx %8lx", next_nonce.uid, next_nonce.ar1_enc);
+        struct Crypto1Params p = {
+            0,
+            next_nonce.nr0_enc,
+            next_nonce.uid ^ next_nonce.nt0,
+            next_nonce.uid ^ next_nonce.nt1,
+            next_nonce.nr1_enc,
+            p64b,
+            next_nonce.ar1_enc};
+        if(!recover(&p, next_nonce.ar0_enc ^ p64, program_state)) {
+            if(program_state->close_thread_please) {
+                break;
+            }
+            // No key found in recover()
+            (program_state->num_completed)++;
+            continue;
+        }
+        (program_state->cracked)++;
+        (program_state->num_completed)++;
+        found_key = p.key;
+        bool already_found = false;
+        for(j = 0; j < keyarray_size; j++) {
+            if(keyarray[j] == found_key) {
+                already_found = true;
+                break;
+            }
+        }
+        if(already_found == false) {
+            // New key
+            keyarray = realloc(keyarray, sizeof(uint64_t) * (keyarray_size + 1)); //-V701
+            keyarray_size += 1;
+            keyarray[keyarray_size - 1] = found_key;
+            (program_state->unique_cracked)++;
+        }
+    }
+    // TODO: Update display to show all keys were found
+    // TODO: Prepend found key(s) to user dictionary file
+    //FURI_LOG_I(TAG, "Unique keys found:");
+    for(i = 0; i < keyarray_size; i++) {
+        //FURI_LOG_I(TAG, "%012" PRIx64, keyarray[i]);
+        FuriString* temp_key = furi_string_alloc();
+        furi_string_cat_printf(temp_key, "%012" PRIX64, keyarray[i]);
+        napi_mf_classic_dict_add_key_str(user_dict, temp_key);
+        furi_string_free(temp_key);
+    }
+    if(keyarray_size > 0) {
+        // TODO: Should we use DolphinDeedNfcMfcAdd?
+        DOLPHIN_DEED(DolphinDeedNfcMfcAdd);
+    }
+    napi_mf_classic_nonce_array_free(nonce_arr);
+    napi_mf_classic_dict_free(user_dict);
+    free(keyarray);
+    //FURI_LOG_I(TAG, "mfkey32 function completed normally"); // DEBUG
+    program_state->mfkey_state = Complete;
+    // No need to alert the user if they asked it to stop
+    if(!(program_state->close_thread_please)) {
+        finished_beep();
+    }
+    return;
+}
+
+// Screen is 128x64 px
+static void render_callback(Canvas* const canvas, void* ctx) {
+    furi_assert(ctx);
+    ProgramState* program_state = ctx;
+    furi_mutex_acquire(program_state->mutex, FuriWaitForever);
+    char draw_str[44] = {};
+    canvas_clear(canvas);
+    canvas_draw_frame(canvas, 0, 0, 128, 64);
+    canvas_draw_frame(canvas, 0, 15, 128, 64);
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, 5, 4, AlignLeft, AlignTop, "Mfkey32");
+    canvas_draw_icon(canvas, 114, 4, &I_mfkey);
+    if(program_state->is_thread_running && program_state->mfkey_state == MfkeyAttack) {
+        float eta_round = (float)1 - ((float)program_state->eta_round / (float)eta_round_time);
+        float eta_total = (float)1 - ((float)program_state->eta_total / (float)eta_total_time);
+        float progress = (float)program_state->num_completed / (float)program_state->total;
+        if(eta_round < 0) {
+            // Round ETA miscalculated
+            eta_round = 1;
+            program_state->eta_round = 0;
+        }
+        if(eta_total < 0) {
+            // Total ETA miscalculated
+            eta_total = 1;
+            program_state->eta_total = 0;
+        }
+        canvas_set_font(canvas, FontSecondary);
+        snprintf(
+            draw_str,
+            sizeof(draw_str),
+            "Cracking: %d/%d - in prog.",
+            program_state->num_completed,
+            program_state->total);
+        elements_progress_bar_with_text(canvas, 5, 18, 118, progress, draw_str);
+        snprintf(
+            draw_str,
+            sizeof(draw_str),
+            "Round: %d/%d - ETA %02d Sec",
+            (program_state->search) + 1, // Zero indexed
+            256 / MSB_LIMIT,
+            program_state->eta_round);
+        elements_progress_bar_with_text(canvas, 5, 31, 118, eta_round, draw_str);
+        snprintf(draw_str, sizeof(draw_str), "Total ETA %03d Sec", program_state->eta_total);
+        elements_progress_bar_with_text(canvas, 5, 44, 118, eta_total, draw_str);
+    } else if(program_state->is_thread_running && program_state->mfkey_state == DictionaryAttack) {
+        canvas_set_font(canvas, FontSecondary);
+        snprintf(
+            draw_str, sizeof(draw_str), "Dict solves: %d (in progress)", program_state->cracked);
+        canvas_draw_str_aligned(canvas, 10, 18, AlignLeft, AlignTop, draw_str);
+        snprintf(draw_str, sizeof(draw_str), "Keys in dict: %d", program_state->dict_count);
+        canvas_draw_str_aligned(canvas, 26, 28, AlignLeft, AlignTop, draw_str);
+    } else if(program_state->mfkey_state == Complete) {
+        // TODO: Scrollable list view to see cracked keys if user presses down
+        elements_progress_bar_with_text(canvas, 5, 18, 118, 1, draw_str);
+        canvas_set_font(canvas, FontSecondary);
+        snprintf(draw_str, sizeof(draw_str), "Complete");
+        canvas_draw_str_aligned(canvas, 40, 31, AlignLeft, AlignTop, draw_str);
+        snprintf(
+            draw_str,
+            sizeof(draw_str),
+            "Keys added to user dict: %d",
+            program_state->unique_cracked);
+        canvas_draw_str_aligned(canvas, 10, 41, AlignLeft, AlignTop, draw_str);
+    } else if(program_state->mfkey_state == Ready) {
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_str_aligned(canvas, 50, 30, AlignLeft, AlignTop, "Ready");
+        elements_button_center(canvas, "Start");
+        elements_button_right(canvas, "Help");
+    } else if(program_state->mfkey_state == Help) {
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_str_aligned(canvas, 7, 20, AlignLeft, AlignTop, "Collect nonces using");
+        canvas_draw_str_aligned(canvas, 7, 30, AlignLeft, AlignTop, "Detect Reader.");
+        canvas_draw_str_aligned(canvas, 7, 40, AlignLeft, AlignTop, "Developers: noproto, AG");
+        canvas_draw_str_aligned(canvas, 7, 50, AlignLeft, AlignTop, "Thanks: bettse");
+    } else if(program_state->mfkey_state == Error) {
+        canvas_draw_str_aligned(canvas, 50, 25, AlignLeft, AlignTop, "Error");
+        canvas_set_font(canvas, FontSecondary);
+        if(program_state->err == MissingNonces) {
+            canvas_draw_str_aligned(canvas, 25, 36, AlignLeft, AlignTop, "No nonces found");
+        } else if(program_state->err == ZeroNonces) {
+            canvas_draw_str_aligned(canvas, 15, 36, AlignLeft, AlignTop, "Nonces already cracked");
+        } else {
+            // Unhandled error
+        }
+    } else {
+        // Unhandled program state
+    }
+    furi_mutex_release(program_state->mutex);
+}
+
+static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
+    furi_assert(event_queue);
+
+    PluginEvent event = {.type = EventTypeKey, .input = *input_event};
+    furi_message_queue_put(event_queue, &event, FuriWaitForever);
+}
+
+static void mfkey32_state_init(ProgramState* program_state) {
+    program_state->is_thread_running = false;
+    program_state->mfkey_state = Ready;
+    program_state->cracked = 0;
+    program_state->unique_cracked = 0;
+    program_state->num_completed = 0;
+    program_state->total = 0;
+    program_state->dict_count = 0;
+}
+
+// Entrypoint for worker thread
+static int32_t mfkey32_worker_thread(void* ctx) {
+    ProgramState* program_state = ctx;
+    program_state->is_thread_running = true;
+    program_state->mfkey_state = Initializing;
+    //FURI_LOG_I(TAG, "Hello from the mfkey32 worker thread"); // DEBUG
+    mfkey32(program_state);
+    program_state->is_thread_running = false;
+    return 0;
+}
+
+void start_mfkey32_thread(ProgramState* program_state) {
+    if(!program_state->is_thread_running) {
+        furi_thread_start(program_state->mfkeythread);
+    }
+}
+
+int32_t mfkey32_main() {
+    FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
+
+    ProgramState* program_state = malloc(sizeof(ProgramState));
+
+    mfkey32_state_init(program_state);
+
+    program_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
+    if(!program_state->mutex) {
+        FURI_LOG_E(TAG, "cannot create mutex\r\n");
+        free(program_state);
+        return 255;
+    }
+
+    // Set system callbacks
+    ViewPort* view_port = view_port_alloc();
+    view_port_draw_callback_set(view_port, render_callback, program_state);
+    view_port_input_callback_set(view_port, input_callback, event_queue);
+
+    // Open GUI and register view_port
+    Gui* gui = furi_record_open(RECORD_GUI);
+    gui_add_view_port(gui, view_port, GuiLayerFullscreen);
+
+    program_state->mfkeythread = furi_thread_alloc();
+    furi_thread_set_name(program_state->mfkeythread, "Mfkey32 Worker");
+    furi_thread_set_stack_size(program_state->mfkeythread, 2048);
+    furi_thread_set_context(program_state->mfkeythread, program_state);
+    furi_thread_set_callback(program_state->mfkeythread, mfkey32_worker_thread);
+
+    PluginEvent event;
+    for(bool main_loop = true; main_loop;) {
+        FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
+
+        furi_mutex_acquire(program_state->mutex, FuriWaitForever);
+
+        if(event_status == FuriStatusOk) {
+            // press events
+            if(event.type == EventTypeKey) {
+                if(event.input.type == InputTypePress) {
+                    switch(event.input.key) {
+                    case InputKeyUp:
+                        break;
+                    case InputKeyDown:
+                        break;
+                    case InputKeyRight:
+                        if(!program_state->is_thread_running &&
+                           program_state->mfkey_state == Ready) {
+                            program_state->mfkey_state = Help;
+                            view_port_update(view_port);
+                        }
+                        break;
+                    case InputKeyLeft:
+                        break;
+                    case InputKeyOk:
+                        if(!program_state->is_thread_running &&
+                           program_state->mfkey_state == Ready) {
+                            start_mfkey32_thread(program_state);
+                            view_port_update(view_port);
+                        }
+                        break;
+                    case InputKeyBack:
+                        if(!program_state->is_thread_running &&
+                           program_state->mfkey_state == Help) {
+                            program_state->mfkey_state = Ready;
+                            view_port_update(view_port);
+                        } else {
+                            program_state->close_thread_please = true;
+                            if(program_state->is_thread_running && program_state->mfkeythread) {
+                                // Wait until thread is finished
+                                furi_thread_join(program_state->mfkeythread);
+                            }
+                            program_state->close_thread_please = false;
+                            main_loop = false;
+                        }
+                        break;
+                    default:
+                        break;
+                    }
+                }
+            }
+        }
+
+        view_port_update(view_port);
+        furi_mutex_release(program_state->mutex);
+    }
+
+    furi_thread_free(program_state->mfkeythread);
+    view_port_enabled_set(view_port, false);
+    gui_remove_view_port(gui, view_port);
+    furi_record_close("gui");
+    view_port_free(view_port);
+    furi_message_queue_free(event_queue);
+    furi_mutex_free(program_state->mutex);
+    free(program_state);
+
+    return 0;
+}

+ 1 - 1
applications/external/picopass/scenes/picopass_scene_read_card_success.c

@@ -34,7 +34,7 @@ void picopass_scene_read_card_success_on_enter(void* context) {
     uint8_t csn[PICOPASS_BLOCK_LEN] = {0};
     memcpy(csn, AA1[PICOPASS_CSN_BLOCK_INDEX].data, PICOPASS_BLOCK_LEN);
     for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
-        furi_string_cat_printf(csn_str, "%02X ", csn[i]);
+        furi_string_cat_printf(csn_str, "%02X", csn[i]);
     }
 
     bool no_key = picopass_is_memset(pacs->key, 0x00, PICOPASS_BLOCK_LEN);

+ 1 - 0
applications/external/weather_station/protocols/protocol_items.c

@@ -16,6 +16,7 @@ const SubGhzProtocol* weather_station_protocol_registry_items[] = {
     &ws_protocol_auriol_th,
     &ws_protocol_oregon_v1,
     &ws_protocol_tx_8300,
+    &ws_protocol_wendox_w6726,
 };
 
 const SubGhzProtocolRegistry weather_station_protocol_registry = {

+ 1 - 0
applications/external/weather_station/protocols/protocol_items.h

@@ -16,5 +16,6 @@
 #include "auriol_hg0601a.h"
 #include "oregon_v1.h"
 #include "tx_8300.h"
+#include "wendox_w6726.h"
 
 extern const SubGhzProtocolRegistry weather_station_protocol_registry;

+ 299 - 0
applications/external/weather_station/protocols/wendox_w6726.c

@@ -0,0 +1,299 @@
+#include "wendox_w6726.h"
+
+#define TAG "WSProtocolWendoxW6726"
+
+/*
+ * Wendox W6726
+ *   
+ *  Temperature -50C to +70C
+ *    _     _     _          __   _
+ *  _| |___| |___| |___ ... |  |_| |__...._______________
+ *    preamble                data           guard time
+ * 
+ *  3 reps every 3 minutes
+ *  in the first message 11 bytes of the preamble in the rest by 7
+ *  
+ *  bit 0: 1955-hi, 5865-lo
+ *  bit 1: 5865-hi, 1955-lo
+ *  guard time: 12*1955+(lo last bit) 
+ *  data: 29 bit
+ * 
+ *  IIIII | ZTTTTTTTTT | uuuuuuuBuu | CCCC
+ * 
+ *  I: identification;
+ *  Z: temperature sign;
+ *  T: temperature sign dependent +12C;
+ *  B: battery low; flag to indicate low battery voltage;
+ *  C: CRC4 (polynomial = 0x9, start_data = 0xD);
+ *  u: unknown; 
+ */
+
+static const SubGhzBlockConst ws_protocol_wendox_w6726_const = {
+    .te_short = 1955,
+    .te_long = 5865,
+    .te_delta = 300,
+    .min_count_bit_for_found = 29,
+};
+
+struct WSProtocolDecoderWendoxW6726 {
+    SubGhzProtocolDecoderBase base;
+
+    SubGhzBlockDecoder decoder;
+    WSBlockGeneric generic;
+
+    uint16_t header_count;
+};
+
+struct WSProtocolEncoderWendoxW6726 {
+    SubGhzProtocolEncoderBase base;
+
+    SubGhzProtocolBlockEncoder encoder;
+    WSBlockGeneric generic;
+};
+
+typedef enum {
+    WendoxW6726DecoderStepReset = 0,
+    WendoxW6726DecoderStepCheckPreambule,
+    WendoxW6726DecoderStepSaveDuration,
+    WendoxW6726DecoderStepCheckDuration,
+} WendoxW6726DecoderStep;
+
+const SubGhzProtocolDecoder ws_protocol_wendox_w6726_decoder = {
+    .alloc = ws_protocol_decoder_wendox_w6726_alloc,
+    .free = ws_protocol_decoder_wendox_w6726_free,
+
+    .feed = ws_protocol_decoder_wendox_w6726_feed,
+    .reset = ws_protocol_decoder_wendox_w6726_reset,
+
+    .get_hash_data = ws_protocol_decoder_wendox_w6726_get_hash_data,
+    .serialize = ws_protocol_decoder_wendox_w6726_serialize,
+    .deserialize = ws_protocol_decoder_wendox_w6726_deserialize,
+    .get_string = ws_protocol_decoder_wendox_w6726_get_string,
+};
+
+const SubGhzProtocolEncoder ws_protocol_wendox_w6726_encoder = {
+    .alloc = NULL,
+    .free = NULL,
+
+    .deserialize = NULL,
+    .stop = NULL,
+    .yield = NULL,
+};
+
+const SubGhzProtocol ws_protocol_wendox_w6726 = {
+    .name = WS_PROTOCOL_WENDOX_W6726_NAME,
+    .type = SubGhzProtocolWeatherStation,
+    .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 |
+            SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable,
+
+    .decoder = &ws_protocol_wendox_w6726_decoder,
+    .encoder = &ws_protocol_wendox_w6726_encoder,
+};
+
+void* ws_protocol_decoder_wendox_w6726_alloc(SubGhzEnvironment* environment) {
+    UNUSED(environment);
+    WSProtocolDecoderWendoxW6726* instance = malloc(sizeof(WSProtocolDecoderWendoxW6726));
+    instance->base.protocol = &ws_protocol_wendox_w6726;
+    instance->generic.protocol_name = instance->base.protocol->name;
+    return instance;
+}
+
+void ws_protocol_decoder_wendox_w6726_free(void* context) {
+    furi_assert(context);
+    WSProtocolDecoderWendoxW6726* instance = context;
+    free(instance);
+}
+
+void ws_protocol_decoder_wendox_w6726_reset(void* context) {
+    furi_assert(context);
+    WSProtocolDecoderWendoxW6726* instance = context;
+    instance->decoder.parser_step = WendoxW6726DecoderStepReset;
+}
+
+static bool ws_protocol_wendox_w6726_check(WSProtocolDecoderWendoxW6726* instance) {
+    if(!instance->decoder.decode_data) return false;
+    uint8_t msg[] = {
+        instance->decoder.decode_data >> 28,
+        instance->decoder.decode_data >> 20,
+        instance->decoder.decode_data >> 12,
+        instance->decoder.decode_data >> 4};
+
+    uint8_t crc = subghz_protocol_blocks_crc4(msg, 4, 0x9, 0xD);
+    return (crc == (instance->decoder.decode_data & 0x0F));
+}
+
+/**
+ * Analysis of received data
+ * @param instance Pointer to a WSBlockGeneric* instance
+ */
+static void ws_protocol_wendox_w6726_remote_controller(WSBlockGeneric* instance) {
+    instance->id = (instance->data >> 24) & 0xFF;
+    instance->battery_low = (instance->data >> 6) & 1;
+    instance->channel = WS_NO_CHANNEL;
+
+    if(((instance->data >> 23) & 1)) {
+        instance->temp = (float)(((instance->data >> 14) & 0x1FF) + 12) / 10.0f;
+    } else {
+        instance->temp = (float)((~(instance->data >> 14) & 0x1FF) + 1 - 12) / -10.0f;
+    }
+
+    if(instance->temp < -50.0f) {
+        instance->temp = -50.0f;
+    } else if(instance->temp > 70.0f) {
+        instance->temp = 70.0f;
+    }
+
+    instance->btn = WS_NO_BTN;
+    instance->humidity = WS_NO_HUMIDITY;
+}
+
+void ws_protocol_decoder_wendox_w6726_feed(void* context, bool level, uint32_t duration) {
+    furi_assert(context);
+    WSProtocolDecoderWendoxW6726* instance = context;
+
+    switch(instance->decoder.parser_step) {
+    case WendoxW6726DecoderStepReset:
+        if((level) && (DURATION_DIFF(duration, ws_protocol_wendox_w6726_const.te_short) <
+                       ws_protocol_wendox_w6726_const.te_delta)) {
+            instance->decoder.parser_step = WendoxW6726DecoderStepCheckPreambule;
+            instance->decoder.te_last = duration;
+            instance->header_count = 0;
+        }
+        break;
+
+    case WendoxW6726DecoderStepCheckPreambule:
+        if(level) {
+            instance->decoder.te_last = duration;
+        } else {
+            if((DURATION_DIFF(instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_short) <
+                ws_protocol_wendox_w6726_const.te_delta * 1) &&
+               (DURATION_DIFF(duration, ws_protocol_wendox_w6726_const.te_long) <
+                ws_protocol_wendox_w6726_const.te_delta * 2)) {
+                instance->header_count++;
+            } else if((instance->header_count > 4) && (instance->header_count < 12)) {
+                if((DURATION_DIFF(
+                        instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_long) <
+                    ws_protocol_wendox_w6726_const.te_delta * 2) &&
+                   (DURATION_DIFF(duration, ws_protocol_wendox_w6726_const.te_short) <
+                    ws_protocol_wendox_w6726_const.te_delta)) {
+                    instance->decoder.decode_data = 0;
+                    instance->decoder.decode_count_bit = 0;
+                    subghz_protocol_blocks_add_bit(&instance->decoder, 1);
+                    instance->decoder.parser_step = WendoxW6726DecoderStepSaveDuration;
+                } else {
+                    instance->decoder.parser_step = WendoxW6726DecoderStepReset;
+                }
+
+            } else {
+                instance->decoder.parser_step = WendoxW6726DecoderStepReset;
+            }
+        }
+        break;
+
+    case WendoxW6726DecoderStepSaveDuration:
+        if(level) {
+            instance->decoder.te_last = duration;
+            instance->decoder.parser_step = WendoxW6726DecoderStepCheckDuration;
+        } else {
+            instance->decoder.parser_step = WendoxW6726DecoderStepReset;
+        }
+        break;
+
+    case WendoxW6726DecoderStepCheckDuration:
+        if(!level) {
+            if(duration >
+               ws_protocol_wendox_w6726_const.te_short + ws_protocol_wendox_w6726_const.te_long) {
+                if(DURATION_DIFF(
+                       instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_short) <
+                   ws_protocol_wendox_w6726_const.te_delta) {
+                    subghz_protocol_blocks_add_bit(&instance->decoder, 0);
+                    instance->decoder.parser_step = WendoxW6726DecoderStepSaveDuration;
+                } else if(
+                    DURATION_DIFF(
+                        instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_long) <
+                    ws_protocol_wendox_w6726_const.te_delta * 2) {
+                    subghz_protocol_blocks_add_bit(&instance->decoder, 1);
+                    instance->decoder.parser_step = WendoxW6726DecoderStepSaveDuration;
+                } else {
+                    instance->decoder.parser_step = WendoxW6726DecoderStepReset;
+                }
+                if((instance->decoder.decode_count_bit ==
+                    ws_protocol_wendox_w6726_const.min_count_bit_for_found) &&
+                   ws_protocol_wendox_w6726_check(instance)) {
+                    instance->generic.data = instance->decoder.decode_data;
+                    instance->generic.data_count_bit = instance->decoder.decode_count_bit;
+                    ws_protocol_wendox_w6726_remote_controller(&instance->generic);
+                    if(instance->base.callback)
+                        instance->base.callback(&instance->base, instance->base.context);
+                }
+
+                instance->decoder.parser_step = WendoxW6726DecoderStepReset;
+            } else if(
+                (DURATION_DIFF(instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_short) <
+                 ws_protocol_wendox_w6726_const.te_delta) &&
+                (DURATION_DIFF(duration, ws_protocol_wendox_w6726_const.te_long) <
+                 ws_protocol_wendox_w6726_const.te_delta * 3)) {
+                subghz_protocol_blocks_add_bit(&instance->decoder, 0);
+                instance->decoder.parser_step = WendoxW6726DecoderStepSaveDuration;
+            } else if(
+                (DURATION_DIFF(instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_long) <
+                 ws_protocol_wendox_w6726_const.te_delta * 2) &&
+                (DURATION_DIFF(duration, ws_protocol_wendox_w6726_const.te_short) <
+                 ws_protocol_wendox_w6726_const.te_delta)) {
+                subghz_protocol_blocks_add_bit(&instance->decoder, 1);
+                instance->decoder.parser_step = WendoxW6726DecoderStepSaveDuration;
+            } else {
+                instance->decoder.parser_step = WendoxW6726DecoderStepReset;
+            }
+        } else {
+            instance->decoder.parser_step = WendoxW6726DecoderStepReset;
+        }
+        break;
+    }
+}
+
+uint8_t ws_protocol_decoder_wendox_w6726_get_hash_data(void* context) {
+    furi_assert(context);
+    WSProtocolDecoderWendoxW6726* instance = context;
+    return subghz_protocol_blocks_get_hash_data(
+        &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
+}
+
+SubGhzProtocolStatus ws_protocol_decoder_wendox_w6726_serialize(
+    void* context,
+    FlipperFormat* flipper_format,
+    SubGhzRadioPreset* preset) {
+    furi_assert(context);
+    WSProtocolDecoderWendoxW6726* instance = context;
+    return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
+}
+
+SubGhzProtocolStatus
+    ws_protocol_decoder_wendox_w6726_deserialize(void* context, FlipperFormat* flipper_format) {
+    furi_assert(context);
+    WSProtocolDecoderWendoxW6726* instance = context;
+    return ws_block_generic_deserialize_check_count_bit(
+        &instance->generic,
+        flipper_format,
+        ws_protocol_wendox_w6726_const.min_count_bit_for_found);
+}
+
+void ws_protocol_decoder_wendox_w6726_get_string(void* context, FuriString* output) {
+    furi_assert(context);
+    WSProtocolDecoderWendoxW6726* instance = context;
+    furi_string_printf(
+        output,
+        "%s %dbit\r\n"
+        "Key:0x%lX%08lX\r\n"
+        "Sn:0x%lX Ch:%d  Bat:%d\r\n"
+        "Temp:%3.1f C Hum:%d%%",
+        instance->generic.protocol_name,
+        instance->generic.data_count_bit,
+        (uint32_t)(instance->generic.data >> 32),
+        (uint32_t)(instance->generic.data),
+        instance->generic.id,
+        instance->generic.channel,
+        instance->generic.battery_low,
+        (double)instance->generic.temp,
+        instance->generic.humidity);
+}

+ 80 - 0
applications/external/weather_station/protocols/wendox_w6726.h

@@ -0,0 +1,80 @@
+#pragma once
+
+#include <lib/subghz/protocols/base.h>
+
+#include <lib/subghz/blocks/const.h>
+#include <lib/subghz/blocks/decoder.h>
+#include <lib/subghz/blocks/encoder.h>
+#include "ws_generic.h"
+#include <lib/subghz/blocks/math.h>
+
+#define WS_PROTOCOL_WENDOX_W6726_NAME "Wendox W6726"
+
+typedef struct WSProtocolDecoderWendoxW6726 WSProtocolDecoderWendoxW6726;
+typedef struct WSProtocolEncoderWendoxW6726 WSProtocolEncoderWendoxW6726;
+
+extern const SubGhzProtocolDecoder ws_protocol_wendox_w6726_decoder;
+extern const SubGhzProtocolEncoder ws_protocol_wendox_w6726_encoder;
+extern const SubGhzProtocol ws_protocol_wendox_w6726;
+
+/**
+ * Allocate WSProtocolDecoderWendoxW6726.
+ * @param environment Pointer to a SubGhzEnvironment instance
+ * @return WSProtocolDecoderWendoxW6726* pointer to a WSProtocolDecoderWendoxW6726 instance
+ */
+void* ws_protocol_decoder_wendox_w6726_alloc(SubGhzEnvironment* environment);
+
+/**
+ * Free WSProtocolDecoderWendoxW6726.
+ * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance
+ */
+void ws_protocol_decoder_wendox_w6726_free(void* context);
+
+/**
+ * Reset decoder WSProtocolDecoderWendoxW6726.
+ * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance
+ */
+void ws_protocol_decoder_wendox_w6726_reset(void* context);
+
+/**
+ * Parse a raw sequence of levels and durations received from the air.
+ * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance
+ * @param level Signal level true-high false-low
+ * @param duration Duration of this level in, us
+ */
+void ws_protocol_decoder_wendox_w6726_feed(void* context, bool level, uint32_t duration);
+
+/**
+ * Getting the hash sum of the last randomly received parcel.
+ * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance
+ * @return hash Hash sum
+ */
+uint8_t ws_protocol_decoder_wendox_w6726_get_hash_data(void* context);
+
+/**
+ * Serialize data WSProtocolDecoderWendoxW6726.
+ * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance
+ * @param flipper_format Pointer to a FlipperFormat instance
+ * @param preset The modulation on which the signal was received, SubGhzRadioPreset
+ * @return status
+ */
+SubGhzProtocolStatus ws_protocol_decoder_wendox_w6726_serialize(
+    void* context,
+    FlipperFormat* flipper_format,
+    SubGhzRadioPreset* preset);
+
+/**
+ * Deserialize data WSProtocolDecoderWendoxW6726.
+ * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance
+ * @param flipper_format Pointer to a FlipperFormat instance
+ * @return status
+ */
+SubGhzProtocolStatus
+    ws_protocol_decoder_wendox_w6726_deserialize(void* context, FlipperFormat* flipper_format);
+
+/**
+ * Getting a textual representation of the received data.
+ * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance
+ * @param output Resulting text
+ */
+void ws_protocol_decoder_wendox_w6726_get_string(void* context, FuriString* output);

+ 3 - 3
applications/external/weather_station/views/weather_station_receiver.c

@@ -12,7 +12,7 @@
 #define MENU_ITEMS 4u
 #define UNLOCK_CNT 3
 
-#define SUBGHZ_RAW_TRESHOLD_MIN -90.0f
+#define SUBGHZ_RAW_THRESHOLD_MIN -90.0f
 typedef struct {
     FuriString* item_str;
     uint8_t type;
@@ -69,10 +69,10 @@ void ws_view_receiver_set_rssi(WSReceiver* instance, float rssi) {
         instance->view,
         WSReceiverModel * model,
         {
-            if(rssi < SUBGHZ_RAW_TRESHOLD_MIN) {
+            if(rssi < SUBGHZ_RAW_THRESHOLD_MIN) {
                 model->u_rssi = 0;
             } else {
-                model->u_rssi = (uint8_t)(rssi - SUBGHZ_RAW_TRESHOLD_MIN);
+                model->u_rssi = (uint8_t)(rssi - SUBGHZ_RAW_THRESHOLD_MIN);
             }
         },
         true);

+ 24 - 17
applications/main/bad_usb/helpers/ducky_script_commands.c

@@ -152,22 +152,22 @@ static int32_t ducky_fnc_waitforbutton(BadUsbScript* bad_usb, const char* line,
 }
 
 static const DuckyCmd ducky_commands[] = {
-    {"REM ", NULL, -1},
-    {"ID ", NULL, -1},
-    {"DELAY ", ducky_fnc_delay, -1},
-    {"STRING ", ducky_fnc_string, 0},
-    {"STRINGLN ", ducky_fnc_string, 1},
-    {"DEFAULT_DELAY ", ducky_fnc_defdelay, -1},
-    {"DEFAULTDELAY ", ducky_fnc_defdelay, -1},
-    {"STRINGDELAY ", ducky_fnc_strdelay, -1},
-    {"STRING_DELAY ", ducky_fnc_strdelay, -1},
-    {"REPEAT ", ducky_fnc_repeat, -1},
-    {"SYSRQ ", ducky_fnc_sysrq, -1},
-    {"ALTCHAR ", ducky_fnc_altchar, -1},
-    {"ALTSTRING ", ducky_fnc_altstring, -1},
-    {"ALTCODE ", ducky_fnc_altstring, -1},
-    {"HOLD ", ducky_fnc_hold, -1},
-    {"RELEASE ", ducky_fnc_release, -1},
+    {"REM", NULL, -1},
+    {"ID", NULL, -1},
+    {"DELAY", ducky_fnc_delay, -1},
+    {"STRING", ducky_fnc_string, 0},
+    {"STRINGLN", ducky_fnc_string, 1},
+    {"DEFAULT_DELAY", ducky_fnc_defdelay, -1},
+    {"DEFAULTDELAY", ducky_fnc_defdelay, -1},
+    {"STRINGDELAY", ducky_fnc_strdelay, -1},
+    {"STRING_DELAY", ducky_fnc_strdelay, -1},
+    {"REPEAT", ducky_fnc_repeat, -1},
+    {"SYSRQ", ducky_fnc_sysrq, -1},
+    {"ALTCHAR", ducky_fnc_altchar, -1},
+    {"ALTSTRING", ducky_fnc_altstring, -1},
+    {"ALTCODE", ducky_fnc_altstring, -1},
+    {"HOLD", ducky_fnc_hold, -1},
+    {"RELEASE", ducky_fnc_release, -1},
     {"WAIT_FOR_BUTTON_PRESS", ducky_fnc_waitforbutton, -1},
 };
 
@@ -175,8 +175,15 @@ static const DuckyCmd ducky_commands[] = {
 #define WORKER_TAG TAG "Worker"
 
 int32_t ducky_execute_cmd(BadUsbScript* bad_usb, const char* line) {
+    size_t cmd_word_len = strcspn(line, " ");
     for(size_t i = 0; i < COUNT_OF(ducky_commands); i++) {
-        if(strncmp(line, ducky_commands[i].name, strlen(ducky_commands[i].name)) == 0) {
+        size_t cmd_compare_len = strlen(ducky_commands[i].name);
+
+        if(cmd_compare_len != cmd_word_len) {
+            continue;
+        }
+
+        if(strncmp(line, ducky_commands[i].name, cmd_compare_len) == 0) {
             if(ducky_commands[i].callback == NULL) {
                 return 0;
             } else {

+ 2 - 2
applications/main/nfc/scenes/nfc_scene_mfkey_complete.c

@@ -18,7 +18,7 @@ void nfc_scene_mfkey_complete_on_enter(void* context) {
         AlignCenter,
         AlignCenter,
         FontSecondary,
-        "Now use mfkey32v2\nto extract keys");
+        "Now use Mfkey32\nto extract keys");
     widget_add_button_element(
         nfc->widget, GuiButtonTypeCenter, "OK", nfc_scene_mfkey_complete_callback, nfc);
 
@@ -46,4 +46,4 @@ void nfc_scene_mfkey_complete_on_exit(void* context) {
     Nfc* nfc = context;
 
     widget_reset(nfc->widget);
-}
+}

+ 2 - 3
applications/main/subghz/helpers/subghz_custom_event.h

@@ -6,14 +6,13 @@ typedef enum {
     SubGhzCustomEventManagerSetRAW,
 
     //SubmenuIndex
-    SubmenuIndexPricenton,
+    SubmenuIndexPricenton_433,
+    SubmenuIndexPricenton_315,
     SubmenuIndexNiceFlo12bit,
     SubmenuIndexNiceFlo24bit,
     SubmenuIndexCAME12bit,
     SubmenuIndexCAME24bit,
     SubmenuIndexCAMETwee,
-    SubmenuIndexNeroSketch,
-    SubmenuIndexNeroRadio,
     SubmenuIndexGateTX,
     SubmenuIndexDoorHan_315_00,
     SubmenuIndexDoorHan_433_92,

+ 1 - 1
applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c

@@ -261,7 +261,7 @@ SubGhzFrequencyAnalyzerWorker* subghz_frequency_analyzer_worker_alloc(void* cont
     instance->thread = furi_thread_alloc_ex(
         "SubGhzFAWorker", 2048, subghz_frequency_analyzer_worker_thread, instance);
     SubGhz* subghz = context;
-    instance->setting = subghz->setting;
+    instance->setting = subghz_txrx_get_setting(subghz->txrx);
     return instance;
 }
 

+ 60 - 0
applications/main/subghz/helpers/subghz_threshold_rssi.c

@@ -0,0 +1,60 @@
+#include "subghz_threshold_rssi.h"
+#include <float_tools.h>
+#include "../subghz_i.h"
+
+#define TAG "SubGhzThresholdRssi"
+#define THRESHOLD_RSSI_LOW_COUNT 10
+
+struct SubGhzThresholdRssi {
+    float threshold_rssi;
+    uint8_t threshold_rssi_low_count;
+};
+
+SubGhzThresholdRssi* subghz_threshold_rssi_alloc(void) {
+    SubGhzThresholdRssi* instance = malloc(sizeof(SubGhzThresholdRssi));
+    instance->threshold_rssi = SUBGHZ_RAW_THRESHOLD_MIN;
+    instance->threshold_rssi_low_count = THRESHOLD_RSSI_LOW_COUNT;
+    return instance;
+}
+
+void subghz_threshold_rssi_free(SubGhzThresholdRssi* instance) {
+    furi_assert(instance);
+    free(instance);
+}
+
+void subghz_threshold_rssi_set(SubGhzThresholdRssi* instance, float rssi) {
+    furi_assert(instance);
+    instance->threshold_rssi = rssi;
+}
+
+float subghz_threshold_rssi_get(SubGhzThresholdRssi* instance) {
+    furi_assert(instance);
+    return instance->threshold_rssi;
+}
+
+SubGhzThresholdRssiData subghz_threshold_get_rssi_data(SubGhzThresholdRssi* instance) {
+    furi_assert(instance);
+    float rssi = furi_hal_subghz_get_rssi();
+    SubGhzThresholdRssiData ret = {.rssi = rssi, .is_above = false};
+
+    if(float_is_equal(instance->threshold_rssi, SUBGHZ_RAW_THRESHOLD_MIN)) {
+        ret.is_above = true;
+    } else {
+        if(rssi < instance->threshold_rssi) {
+            instance->threshold_rssi_low_count++;
+            if(instance->threshold_rssi_low_count > THRESHOLD_RSSI_LOW_COUNT) {
+                instance->threshold_rssi_low_count = THRESHOLD_RSSI_LOW_COUNT;
+            }
+            ret.is_above = false;
+        } else {
+            instance->threshold_rssi_low_count = 0;
+        }
+
+        if(instance->threshold_rssi_low_count == THRESHOLD_RSSI_LOW_COUNT) {
+            ret.is_above = false;
+        } else {
+            ret.is_above = true;
+        }
+    }
+    return ret;
+}

+ 43 - 0
applications/main/subghz/helpers/subghz_threshold_rssi.h

@@ -0,0 +1,43 @@
+#pragma once
+
+#include <furi.h>
+
+typedef struct {
+    float rssi; /**< Current RSSI */
+    bool is_above; /**< Exceeded threshold level */
+} SubGhzThresholdRssiData;
+
+typedef struct SubGhzThresholdRssi SubGhzThresholdRssi;
+
+/** Allocate SubGhzThresholdRssi
+ * 
+ * @return SubGhzThresholdRssi* 
+ */
+SubGhzThresholdRssi* subghz_threshold_rssi_alloc(void);
+
+/** Free SubGhzThresholdRssi
+ * 
+ * @param instance Pointer to a SubGhzThresholdRssi
+ */
+void subghz_threshold_rssi_free(SubGhzThresholdRssi* instance);
+
+/** Set threshold
+ * 
+ * @param instance Pointer to a SubGhzThresholdRssi
+ * @param rssi RSSI threshold
+ */
+void subghz_threshold_rssi_set(SubGhzThresholdRssi* instance, float rssi);
+
+/** Get threshold
+ * 
+ * @param instance Pointer to a SubGhzThresholdRssi
+ * @return float RSSI threshold
+ */
+float subghz_threshold_rssi_get(SubGhzThresholdRssi* instance);
+
+/** Check threshold
+ * 
+ * @param instance Pointer to a SubGhzThresholdRssi
+ * @return SubGhzThresholdRssiData 
+ */
+SubGhzThresholdRssiData subghz_threshold_get_rssi_data(SubGhzThresholdRssi* instance);

+ 521 - 0
applications/main/subghz/helpers/subghz_txrx.c

@@ -0,0 +1,521 @@
+#include "subghz_txrx_i.h"
+
+#include <lib/subghz/protocols/protocol_items.h>
+
+#define TAG "SubGhz"
+
+SubGhzTxRx* subghz_txrx_alloc() {
+    SubGhzTxRx* instance = malloc(sizeof(SubGhzTxRx));
+    instance->setting = subghz_setting_alloc();
+    subghz_setting_load(instance->setting, EXT_PATH("subghz/assets/setting_user"));
+
+    instance->preset = malloc(sizeof(SubGhzRadioPreset));
+    instance->preset->name = furi_string_alloc();
+    subghz_txrx_set_preset(
+        instance, "AM650", subghz_setting_get_default_frequency(instance->setting), NULL, 0);
+
+    instance->txrx_state = SubGhzTxRxStateSleep;
+
+    subghz_txrx_hopper_set_state(instance, SubGhzHopperStateOFF);
+    subghz_txrx_speaker_set_state(instance, SubGhzSpeakerStateDisable);
+
+    instance->worker = subghz_worker_alloc();
+    instance->fff_data = flipper_format_string_alloc();
+
+    instance->environment = subghz_environment_alloc();
+    instance->is_database_loaded = subghz_environment_load_keystore(
+        instance->environment, EXT_PATH("subghz/assets/keeloq_mfcodes"));
+    subghz_environment_load_keystore(
+        instance->environment, EXT_PATH("subghz/assets/keeloq_mfcodes_user"));
+    subghz_environment_set_came_atomo_rainbow_table_file_name(
+        instance->environment, EXT_PATH("subghz/assets/came_atomo"));
+    subghz_environment_set_alutech_at_4n_rainbow_table_file_name(
+        instance->environment, EXT_PATH("subghz/assets/alutech_at_4n"));
+    subghz_environment_set_nice_flor_s_rainbow_table_file_name(
+        instance->environment, EXT_PATH("subghz/assets/nice_flor_s"));
+    subghz_environment_set_protocol_registry(
+        instance->environment, (void*)&subghz_protocol_registry);
+    instance->receiver = subghz_receiver_alloc_init(instance->environment);
+
+    subghz_worker_set_overrun_callback(
+        instance->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset);
+    subghz_worker_set_pair_callback(
+        instance->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode);
+    subghz_worker_set_context(instance->worker, instance->receiver);
+
+    return instance;
+}
+
+void subghz_txrx_free(SubGhzTxRx* instance) {
+    furi_assert(instance);
+
+    subghz_worker_free(instance->worker);
+    subghz_receiver_free(instance->receiver);
+    subghz_environment_free(instance->environment);
+    flipper_format_free(instance->fff_data);
+    furi_string_free(instance->preset->name);
+    subghz_setting_free(instance->setting);
+    free(instance->preset);
+    free(instance);
+}
+
+bool subghz_txrx_is_database_loaded(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return instance->is_database_loaded;
+}
+
+void subghz_txrx_set_preset(
+    SubGhzTxRx* instance,
+    const char* preset_name,
+    uint32_t frequency,
+    uint8_t* preset_data,
+    size_t preset_data_size) {
+    furi_assert(instance);
+    furi_string_set(instance->preset->name, preset_name);
+    SubGhzRadioPreset* preset = instance->preset;
+    preset->frequency = frequency;
+    preset->data = preset_data;
+    preset->data_size = preset_data_size;
+}
+
+const char* subghz_txrx_get_preset_name(SubGhzTxRx* instance, const char* preset) {
+    UNUSED(instance);
+    const char* preset_name = "";
+    if(!strcmp(preset, "FuriHalSubGhzPresetOok270Async")) {
+        preset_name = "AM270";
+    } else if(!strcmp(preset, "FuriHalSubGhzPresetOok650Async")) {
+        preset_name = "AM650";
+    } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev238Async")) {
+        preset_name = "FM238";
+    } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev476Async")) {
+        preset_name = "FM476";
+    } else if(!strcmp(preset, "FuriHalSubGhzPresetCustom")) {
+        preset_name = "CUSTOM";
+    } else {
+        FURI_LOG_E(TAG, "Unknown preset");
+    }
+    return preset_name;
+}
+
+SubGhzRadioPreset subghz_txrx_get_preset(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return *instance->preset;
+}
+
+void subghz_txrx_get_frequency_and_modulation(
+    SubGhzTxRx* instance,
+    FuriString* frequency,
+    FuriString* modulation) {
+    furi_assert(instance);
+    SubGhzRadioPreset* preset = instance->preset;
+    if(frequency != NULL) {
+        furi_string_printf(
+            frequency,
+            "%03ld.%02ld",
+            preset->frequency / 1000000 % 1000,
+            preset->frequency / 10000 % 100);
+    }
+    if(modulation != NULL) {
+        furi_string_printf(modulation, "%.2s", furi_string_get_cstr(preset->name));
+    }
+}
+
+static void subghz_txrx_begin(SubGhzTxRx* instance, uint8_t* preset_data) {
+    furi_assert(instance);
+    furi_hal_subghz_reset();
+    furi_hal_subghz_idle();
+    furi_hal_subghz_load_custom_preset(preset_data);
+    furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow);
+    instance->txrx_state = SubGhzTxRxStateIDLE;
+}
+
+static uint32_t subghz_txrx_rx(SubGhzTxRx* instance, uint32_t frequency) {
+    furi_assert(instance);
+    if(!furi_hal_subghz_is_frequency_valid(frequency)) {
+        furi_crash("SubGhz: Incorrect RX frequency.");
+    }
+    furi_assert(
+        instance->txrx_state != SubGhzTxRxStateRx && instance->txrx_state != SubGhzTxRxStateSleep);
+
+    furi_hal_subghz_idle();
+    uint32_t value = furi_hal_subghz_set_frequency_and_path(frequency);
+    furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow);
+    furi_hal_subghz_flush_rx();
+    subghz_txrx_speaker_on(instance);
+    furi_hal_subghz_rx();
+
+    furi_hal_subghz_start_async_rx(subghz_worker_rx_callback, instance->worker);
+    subghz_worker_start(instance->worker);
+    instance->txrx_state = SubGhzTxRxStateRx;
+    return value;
+}
+
+static void subghz_txrx_idle(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    furi_assert(instance->txrx_state != SubGhzTxRxStateSleep);
+    furi_hal_subghz_idle();
+    subghz_txrx_speaker_off(instance);
+    instance->txrx_state = SubGhzTxRxStateIDLE;
+}
+
+static void subghz_txrx_rx_end(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    furi_assert(instance->txrx_state == SubGhzTxRxStateRx);
+
+    if(subghz_worker_is_running(instance->worker)) {
+        subghz_worker_stop(instance->worker);
+        furi_hal_subghz_stop_async_rx();
+    }
+    furi_hal_subghz_idle();
+    subghz_txrx_speaker_off(instance);
+    instance->txrx_state = SubGhzTxRxStateIDLE;
+}
+
+void subghz_txrx_sleep(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    furi_hal_subghz_sleep();
+    instance->txrx_state = SubGhzTxRxStateSleep;
+}
+
+static bool subghz_txrx_tx(SubGhzTxRx* instance, uint32_t frequency) {
+    furi_assert(instance);
+    if(!furi_hal_subghz_is_frequency_valid(frequency)) {
+        furi_crash("SubGhz: Incorrect TX frequency.");
+    }
+    furi_assert(instance->txrx_state != SubGhzTxRxStateSleep);
+    furi_hal_subghz_idle();
+    furi_hal_subghz_set_frequency_and_path(frequency);
+    furi_hal_gpio_write(&gpio_cc1101_g0, false);
+    furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
+    bool ret = furi_hal_subghz_tx();
+    if(ret) {
+        subghz_txrx_speaker_on(instance);
+        instance->txrx_state = SubGhzTxRxStateTx;
+    }
+
+    return ret;
+}
+
+SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat* flipper_format) {
+    furi_assert(instance);
+    furi_assert(flipper_format);
+
+    subghz_txrx_stop(instance);
+
+    SubGhzTxRxStartTxState ret = SubGhzTxRxStartTxStateErrorParserOthers;
+    FuriString* temp_str = furi_string_alloc();
+    uint32_t repeat = 200;
+    do {
+        if(!flipper_format_rewind(flipper_format)) {
+            FURI_LOG_E(TAG, "Rewind error");
+            break;
+        }
+        if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) {
+            FURI_LOG_E(TAG, "Missing Protocol");
+            break;
+        }
+        if(!flipper_format_insert_or_update_uint32(flipper_format, "Repeat", &repeat, 1)) {
+            FURI_LOG_E(TAG, "Unable Repeat");
+            break;
+        }
+        ret = SubGhzTxRxStartTxStateOk;
+
+        SubGhzRadioPreset* preset = instance->preset;
+        instance->transmitter =
+            subghz_transmitter_alloc_init(instance->environment, furi_string_get_cstr(temp_str));
+
+        if(instance->transmitter) {
+            if(subghz_transmitter_deserialize(instance->transmitter, flipper_format) ==
+               SubGhzProtocolStatusOk) {
+                if(strcmp(furi_string_get_cstr(preset->name), "") != 0) {
+                    subghz_txrx_begin(
+                        instance,
+                        subghz_setting_get_preset_data_by_name(
+                            instance->setting, furi_string_get_cstr(preset->name)));
+                    if(preset->frequency) {
+                        if(!subghz_txrx_tx(instance, preset->frequency)) {
+                            FURI_LOG_E(TAG, "Only Rx");
+                            ret = SubGhzTxRxStartTxStateErrorOnlyRx;
+                        }
+                    } else {
+                        ret = SubGhzTxRxStartTxStateErrorParserOthers;
+                    }
+
+                } else {
+                    FURI_LOG_E(
+                        TAG, "Unknown name preset \" %s \"", furi_string_get_cstr(preset->name));
+                    ret = SubGhzTxRxStartTxStateErrorParserOthers;
+                }
+
+                if(ret == SubGhzTxRxStartTxStateOk) {
+                    //Start TX
+                    furi_hal_subghz_start_async_tx(
+                        subghz_transmitter_yield, instance->transmitter);
+                }
+            } else {
+                ret = SubGhzTxRxStartTxStateErrorParserOthers;
+            }
+        } else {
+            ret = SubGhzTxRxStartTxStateErrorParserOthers;
+        }
+        if(ret != SubGhzTxRxStartTxStateOk) {
+            subghz_transmitter_free(instance->transmitter);
+            if(instance->txrx_state != SubGhzTxRxStateIDLE) {
+                subghz_txrx_idle(instance);
+            }
+        }
+
+    } while(false);
+    furi_string_free(temp_str);
+    return ret;
+}
+
+void subghz_txrx_rx_start(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    subghz_txrx_stop(instance);
+    subghz_txrx_begin(
+        instance,
+        subghz_setting_get_preset_data_by_name(
+            subghz_txrx_get_setting(instance), furi_string_get_cstr(instance->preset->name)));
+    subghz_txrx_rx(instance, instance->preset->frequency);
+}
+
+void subghz_txrx_set_need_save_callback(
+    SubGhzTxRx* instance,
+    SubGhzTxRxNeedSaveCallback callback,
+    void* context) {
+    furi_assert(instance);
+    instance->need_save_callback = callback;
+    instance->need_save_context = context;
+}
+
+static void subghz_txrx_tx_stop(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    furi_assert(instance->txrx_state == SubGhzTxRxStateTx);
+    //Stop TX
+    furi_hal_subghz_stop_async_tx();
+    subghz_transmitter_stop(instance->transmitter);
+    subghz_transmitter_free(instance->transmitter);
+
+    //if protocol dynamic then we save the last upload
+    if(instance->decoder_result->protocol->type == SubGhzProtocolTypeDynamic) {
+        if(instance->need_save_callback) {
+            instance->need_save_callback(instance->need_save_context);
+        }
+    }
+    subghz_txrx_idle(instance);
+    subghz_txrx_speaker_off(instance);
+    //Todo: Show message
+    // notification_message(notifications, &sequence_reset_red);
+}
+
+FlipperFormat* subghz_txrx_get_fff_data(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return instance->fff_data;
+}
+
+SubGhzSetting* subghz_txrx_get_setting(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return instance->setting;
+}
+
+void subghz_txrx_stop(SubGhzTxRx* instance) {
+    furi_assert(instance);
+
+    switch(instance->txrx_state) {
+    case SubGhzTxRxStateTx:
+        subghz_txrx_tx_stop(instance);
+        subghz_txrx_speaker_unmute(instance);
+        break;
+    case SubGhzTxRxStateRx:
+        subghz_txrx_rx_end(instance);
+        subghz_txrx_speaker_mute(instance);
+        break;
+
+    default:
+        break;
+    }
+}
+
+void subghz_txrx_hopper_update(SubGhzTxRx* instance) {
+    furi_assert(instance);
+
+    switch(instance->hopper_state) {
+    case SubGhzHopperStateOFF:
+    case SubGhzHopperStatePause:
+        return;
+    case SubGhzHopperStateRSSITimeOut:
+        if(instance->hopper_timeout != 0) {
+            instance->hopper_timeout--;
+            return;
+        }
+        break;
+    default:
+        break;
+    }
+    float rssi = -127.0f;
+    if(instance->hopper_state != SubGhzHopperStateRSSITimeOut) {
+        // See RSSI Calculation timings in CC1101 17.3 RSSI
+        rssi = furi_hal_subghz_get_rssi();
+
+        // Stay if RSSI is high enough
+        if(rssi > -90.0f) {
+            instance->hopper_timeout = 10;
+            instance->hopper_state = SubGhzHopperStateRSSITimeOut;
+            return;
+        }
+    } else {
+        instance->hopper_state = SubGhzHopperStateRunnig;
+    }
+    // Select next frequency
+    if(instance->hopper_idx_frequency <
+       subghz_setting_get_hopper_frequency_count(instance->setting) - 1) {
+        instance->hopper_idx_frequency++;
+    } else {
+        instance->hopper_idx_frequency = 0;
+    }
+
+    if(instance->txrx_state == SubGhzTxRxStateRx) {
+        subghz_txrx_rx_end(instance);
+    };
+    if(instance->txrx_state == SubGhzTxRxStateIDLE) {
+        subghz_receiver_reset(instance->receiver);
+        instance->preset->frequency =
+            subghz_setting_get_hopper_frequency(instance->setting, instance->hopper_idx_frequency);
+        subghz_txrx_rx(instance, instance->preset->frequency);
+    }
+}
+
+SubGhzHopperState subghz_txrx_hopper_get_state(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return instance->hopper_state;
+}
+
+void subghz_txrx_hopper_set_state(SubGhzTxRx* instance, SubGhzHopperState state) {
+    furi_assert(instance);
+    instance->hopper_state = state;
+}
+
+void subghz_txrx_hopper_unpause(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    if(instance->hopper_state == SubGhzHopperStatePause) {
+        instance->hopper_state = SubGhzHopperStateRunnig;
+    }
+}
+
+void subghz_txrx_hopper_pause(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    if(instance->hopper_state == SubGhzHopperStateRunnig) {
+        instance->hopper_state = SubGhzHopperStatePause;
+    }
+}
+
+void subghz_txrx_speaker_on(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    if(instance->speaker_state == SubGhzSpeakerStateEnable) {
+        if(furi_hal_speaker_acquire(30)) {
+            furi_hal_subghz_set_async_mirror_pin(&gpio_speaker);
+        } else {
+            instance->speaker_state = SubGhzSpeakerStateDisable;
+        }
+    }
+}
+
+void subghz_txrx_speaker_off(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    if(instance->speaker_state != SubGhzSpeakerStateDisable) {
+        if(furi_hal_speaker_is_mine()) {
+            furi_hal_subghz_set_async_mirror_pin(NULL);
+            furi_hal_speaker_release();
+            if(instance->speaker_state == SubGhzSpeakerStateShutdown)
+                instance->speaker_state = SubGhzSpeakerStateDisable;
+        }
+    }
+}
+
+void subghz_txrx_speaker_mute(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    if(instance->speaker_state == SubGhzSpeakerStateEnable) {
+        if(furi_hal_speaker_is_mine()) {
+            furi_hal_subghz_set_async_mirror_pin(NULL);
+        }
+    }
+}
+
+void subghz_txrx_speaker_unmute(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    if(instance->speaker_state == SubGhzSpeakerStateEnable) {
+        if(furi_hal_speaker_is_mine()) {
+            furi_hal_subghz_set_async_mirror_pin(&gpio_speaker);
+        }
+    }
+}
+
+void subghz_txrx_speaker_set_state(SubGhzTxRx* instance, SubGhzSpeakerState state) {
+    furi_assert(instance);
+    instance->speaker_state = state;
+}
+
+SubGhzSpeakerState subghz_txrx_speaker_get_state(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return instance->speaker_state;
+}
+
+bool subghz_txrx_load_decoder_by_name_protocol(SubGhzTxRx* instance, const char* name_protocol) {
+    furi_assert(instance);
+    furi_assert(name_protocol);
+    bool res = false;
+    instance->decoder_result =
+        subghz_receiver_search_decoder_base_by_name(instance->receiver, name_protocol);
+    if(instance->decoder_result) {
+        res = true;
+    }
+    return res;
+}
+
+SubGhzProtocolDecoderBase* subghz_txrx_get_decoder(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return instance->decoder_result;
+}
+
+bool subghz_txrx_protocol_is_serializable(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return (
+        (instance->decoder_result->protocol->flag & SubGhzProtocolFlag_Save) ==
+        SubGhzProtocolFlag_Save);
+}
+
+bool subghz_txrx_protocol_is_transmittable(SubGhzTxRx* instance, bool check_type) {
+    furi_assert(instance);
+    const SubGhzProtocol* protocol = instance->decoder_result->protocol;
+    if(check_type) {
+        return (
+            ((protocol->flag & SubGhzProtocolFlag_Send) == SubGhzProtocolFlag_Send) &&
+            protocol->encoder->deserialize && protocol->type == SubGhzProtocolTypeStatic);
+    }
+    return (
+        ((protocol->flag & SubGhzProtocolFlag_Send) == SubGhzProtocolFlag_Send) &&
+        protocol->encoder->deserialize);
+}
+
+void subghz_txrx_receiver_set_filter(SubGhzTxRx* instance, SubGhzProtocolFlag filter) {
+    furi_assert(instance);
+    subghz_receiver_set_filter(instance->receiver, filter);
+}
+
+void subghz_txrx_set_rx_calback(
+    SubGhzTxRx* instance,
+    SubGhzReceiverCallback callback,
+    void* context) {
+    subghz_receiver_set_rx_callback(instance->receiver, callback, context);
+}
+
+void subghz_txrx_set_raw_file_encoder_worker_callback_end(
+    SubGhzTxRx* instance,
+    SubGhzProtocolEncoderRAWCallbackEnd callback,
+    void* context) {
+    subghz_protocol_raw_file_encoder_worker_set_callback_end(
+        (SubGhzProtocolEncoderRAW*)subghz_transmitter_get_protocol_instance(instance->transmitter),
+        callback,
+        context);
+}

+ 290 - 0
applications/main/subghz/helpers/subghz_txrx.h

@@ -0,0 +1,290 @@
+#pragma once
+
+#include "subghz_types.h"
+
+#include <lib/subghz/subghz_worker.h>
+#include <lib/subghz/subghz_setting.h>
+#include <lib/subghz/receiver.h>
+#include <lib/subghz/transmitter.h>
+#include <lib/subghz/protocols/raw.h>
+
+typedef struct SubGhzTxRx SubGhzTxRx;
+
+typedef void (*SubGhzTxRxNeedSaveCallback)(void* context);
+
+typedef enum {
+    SubGhzTxRxStartTxStateOk,
+    SubGhzTxRxStartTxStateErrorOnlyRx,
+    SubGhzTxRxStartTxStateErrorParserOthers,
+} SubGhzTxRxStartTxState;
+
+/**
+ * Allocate SubGhzTxRx
+ * 
+ * @return SubGhzTxRx* pointer to SubGhzTxRx
+ */
+SubGhzTxRx* subghz_txrx_alloc();
+
+/**
+ * Free SubGhzTxRx
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ */
+void subghz_txrx_free(SubGhzTxRx* instance);
+
+/**
+ * Check if the database is loaded
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @return bool True if the database is loaded
+ */
+bool subghz_txrx_is_database_loaded(SubGhzTxRx* instance);
+
+/**
+ * Set preset 
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param preset_name Name of preset
+ * @param frequency Frequency in Hz
+ * @param preset_data Data of preset
+ * @param preset_data_size Size of preset data
+ */
+void subghz_txrx_set_preset(
+    SubGhzTxRx* instance,
+    const char* preset_name,
+    uint32_t frequency,
+    uint8_t* preset_data,
+    size_t preset_data_size);
+
+/**
+ * Get name of preset
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param preset String of preset 
+ * @return const char*  Name of preset
+ */
+const char* subghz_txrx_get_preset_name(SubGhzTxRx* instance, const char* preset);
+
+/**
+ * Get of preset
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @return SubGhzRadioPreset Preset
+ */
+SubGhzRadioPreset subghz_txrx_get_preset(SubGhzTxRx* instance);
+
+/**
+ * Get string frequency and modulation
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param frequency Pointer to a string frequency
+ * @param modulation Pointer to a string modulation
+ */
+void subghz_txrx_get_frequency_and_modulation(
+    SubGhzTxRx* instance,
+    FuriString* frequency,
+    FuriString* modulation);
+
+/**
+ * Start TX CC1101
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param flipper_format Pointer to a FlipperFormat
+ * @return SubGhzTxRxStartTxState 
+ */
+SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat* flipper_format);
+
+/**
+ * Start RX CC1101
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ */
+void subghz_txrx_rx_start(SubGhzTxRx* instance);
+
+/**
+ * Stop TX/RX CC1101
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ */
+void subghz_txrx_stop(SubGhzTxRx* instance);
+
+/**
+ * Set sleep mode CC1101
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ */
+void subghz_txrx_sleep(SubGhzTxRx* instance);
+
+/**
+ * Update frequency CC1101 in automatic mode (hopper)
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ */
+void subghz_txrx_hopper_update(SubGhzTxRx* instance);
+
+/**
+ * Get state hopper
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @return SubGhzHopperState 
+ */
+SubGhzHopperState subghz_txrx_hopper_get_state(SubGhzTxRx* instance);
+
+/**
+ * Set state hopper
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param state State hopper
+ */
+void subghz_txrx_hopper_set_state(SubGhzTxRx* instance, SubGhzHopperState state);
+
+/**
+ * Unpause hopper
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ */
+void subghz_txrx_hopper_unpause(SubGhzTxRx* instance);
+
+/**
+ * Set pause hopper
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ */
+void subghz_txrx_hopper_pause(SubGhzTxRx* instance);
+
+/**
+ * Speaker on
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ */
+void subghz_txrx_speaker_on(SubGhzTxRx* instance);
+
+/**
+ * Speaker off
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ */
+void subghz_txrx_speaker_off(SubGhzTxRx* instance);
+
+/**
+ * Speaker mute
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ */
+void subghz_txrx_speaker_mute(SubGhzTxRx* instance);
+
+/**
+ * Speaker unmute
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ */
+void subghz_txrx_speaker_unmute(SubGhzTxRx* instance);
+
+/**
+ * Set state speaker
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ * @param state State speaker
+ */
+void subghz_txrx_speaker_set_state(SubGhzTxRx* instance, SubGhzSpeakerState state);
+
+/**
+ * Get state speaker
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ * @return SubGhzSpeakerState 
+ */
+SubGhzSpeakerState subghz_txrx_speaker_get_state(SubGhzTxRx* instance);
+
+/**
+ * load decoder by name protocol
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param name_protocol Name protocol
+ * @return bool True if the decoder is loaded 
+ */
+bool subghz_txrx_load_decoder_by_name_protocol(SubGhzTxRx* instance, const char* name_protocol);
+
+/**
+ * Get decoder
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @return SubGhzProtocolDecoderBase* Pointer to a SubGhzProtocolDecoderBase
+ */
+SubGhzProtocolDecoderBase* subghz_txrx_get_decoder(SubGhzTxRx* instance);
+
+/**
+ * Set callback for save data
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param callback Callback for save data
+ * @param context Context for callback
+ */
+void subghz_txrx_set_need_save_callback(
+    SubGhzTxRx* instance,
+    SubGhzTxRxNeedSaveCallback callback,
+    void* context);
+
+/**
+ * Get pointer to a load data key
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @return FlipperFormat* 
+ */
+FlipperFormat* subghz_txrx_get_fff_data(SubGhzTxRx* instance);
+
+/**
+ * Get pointer to a SugGhzSetting
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @return SubGhzSetting* 
+ */
+SubGhzSetting* subghz_txrx_get_setting(SubGhzTxRx* instance);
+
+/**
+ * Is it possible to save this protocol
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ * @return bool True if it is possible to save this protocol
+ */
+bool subghz_txrx_protocol_is_serializable(SubGhzTxRx* instance);
+
+/**
+ * Is it possible to send this protocol
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ * @return bool True if it is possible to send this protocol
+ */
+bool subghz_txrx_protocol_is_transmittable(SubGhzTxRx* instance, bool check_type);
+
+/**
+ * Set filter, what types of decoder to use 
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param filter Filter
+ */
+void subghz_txrx_receiver_set_filter(SubGhzTxRx* instance, SubGhzProtocolFlag filter);
+
+/**
+ * Set callback for receive data
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param callback Callback for receive data
+ * @param context Context for callback
+ */
+void subghz_txrx_set_rx_calback(
+    SubGhzTxRx* instance,
+    SubGhzReceiverCallback callback,
+    void* context);
+
+/**
+ * Set callback for Raw decoder, end of data transfer  
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param callback Callback for Raw decoder, end of data transfer 
+ * @param context Context for callback
+ */
+void subghz_txrx_set_raw_file_encoder_worker_callback_end(
+    SubGhzTxRx* instance,
+    SubGhzProtocolEncoderRAWCallbackEnd callback,
+    void* context);

+ 164 - 0
applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c

@@ -0,0 +1,164 @@
+#include "subghz_txrx_i.h"
+#include "subghz_txrx_create_protocol_key.h"
+#include <lib/subghz/transmitter.h>
+#include <lib/subghz/protocols/protocol_items.h>
+#include <lib/subghz/protocols/protocol_items.h>
+#include <lib/subghz/protocols/keeloq.h>
+#include <lib/subghz/protocols/secplus_v1.h>
+#include <lib/subghz/protocols/secplus_v2.h>
+
+#include <flipper_format/flipper_format_i.h>
+#include <lib/toolbox/stream/stream.h>
+#include <lib/subghz/protocols/raw.h>
+
+#define TAG "SubGhzCreateProtocolKey"
+
+bool subghz_txrx_gen_data_protocol(
+    void* context,
+    const char* preset_name,
+    uint32_t frequency,
+    const char* protocol_name,
+    uint64_t key,
+    uint32_t bit) {
+    furi_assert(context);
+    SubGhzTxRx* instance = context;
+
+    bool res = false;
+
+    subghz_txrx_set_preset(instance, preset_name, frequency, NULL, 0);
+    instance->decoder_result =
+        subghz_receiver_search_decoder_base_by_name(instance->receiver, protocol_name);
+
+    if(instance->decoder_result == NULL) {
+        //TODO: Error
+        // furi_string_set(error_str, "Protocol not\nfound!");
+        // scene_manager_next_scene(scene_manager, SubGhzSceneShowErrorSub);
+        FURI_LOG_E(TAG, "Protocol not found!");
+        return false;
+    }
+
+    do {
+        Stream* fff_data_stream = flipper_format_get_raw_stream(instance->fff_data);
+        stream_clean(fff_data_stream);
+        if(subghz_protocol_decoder_base_serialize(
+               instance->decoder_result, instance->fff_data, instance->preset) !=
+           SubGhzProtocolStatusOk) {
+            FURI_LOG_E(TAG, "Unable to serialize");
+            break;
+        }
+        if(!flipper_format_update_uint32(instance->fff_data, "Bit", &bit, 1)) {
+            FURI_LOG_E(TAG, "Unable to update Bit");
+            break;
+        }
+
+        uint8_t key_data[sizeof(uint64_t)] = {0};
+        for(size_t i = 0; i < sizeof(uint64_t); i++) {
+            key_data[sizeof(uint64_t) - i - 1] = (key >> (i * 8)) & 0xFF;
+        }
+        if(!flipper_format_update_hex(instance->fff_data, "Key", key_data, sizeof(uint64_t))) {
+            FURI_LOG_E(TAG, "Unable to update Key");
+            break;
+        }
+        res = true;
+    } while(false);
+    return res;
+}
+
+bool subghz_txrx_gen_data_protocol_and_te(
+    SubGhzTxRx* instance,
+    const char* preset_name,
+    uint32_t frequency,
+    const char* protocol_name,
+    uint64_t key,
+    uint32_t bit,
+    uint32_t te) {
+    furi_assert(instance);
+    bool ret = false;
+    if(subghz_txrx_gen_data_protocol(instance, preset_name, frequency, protocol_name, key, bit)) {
+        if(!flipper_format_update_uint32(instance->fff_data, "TE", (uint32_t*)&te, 1)) {
+            FURI_LOG_E(TAG, "Unable to update Te");
+        } else {
+            ret = true;
+        }
+    }
+    return ret;
+}
+
+bool subghz_txrx_gen_keeloq_protocol(
+    SubGhzTxRx* instance,
+    const char* name_preset,
+    uint32_t frequency,
+    const char* name_sysmem,
+    uint32_t serial,
+    uint8_t btn,
+    uint16_t cnt) {
+    furi_assert(instance);
+
+    bool ret = false;
+    serial &= 0x0FFFFFFF;
+    instance->transmitter =
+        subghz_transmitter_alloc_init(instance->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME);
+    subghz_txrx_set_preset(instance, name_preset, frequency, NULL, 0);
+    if(instance->transmitter) {
+        subghz_protocol_keeloq_create_data(
+            subghz_transmitter_get_protocol_instance(instance->transmitter),
+            instance->fff_data,
+            serial,
+            btn,
+            cnt,
+            name_sysmem,
+            instance->preset);
+        ret = true;
+    }
+    subghz_transmitter_free(instance->transmitter);
+    return ret;
+}
+
+bool subghz_txrx_gen_secplus_v2_protocol(
+    SubGhzTxRx* instance,
+    const char* name_preset,
+    uint32_t frequency,
+    uint32_t serial,
+    uint8_t btn,
+    uint32_t cnt) {
+    furi_assert(instance);
+
+    bool ret = false;
+    instance->transmitter =
+        subghz_transmitter_alloc_init(instance->environment, SUBGHZ_PROTOCOL_SECPLUS_V2_NAME);
+    subghz_txrx_set_preset(instance, name_preset, frequency, NULL, 0);
+    if(instance->transmitter) {
+        subghz_protocol_secplus_v2_create_data(
+            subghz_transmitter_get_protocol_instance(instance->transmitter),
+            instance->fff_data,
+            serial,
+            btn,
+            cnt,
+            instance->preset);
+        ret = true;
+    }
+    return ret;
+}
+
+bool subghz_txrx_gen_secplus_v1_protocol(
+    SubGhzTxRx* instance,
+    const char* name_preset,
+    uint32_t frequency) {
+    furi_assert(instance);
+
+    bool ret = false;
+    uint32_t serial = (uint32_t)rand();
+    while(!subghz_protocol_secplus_v1_check_fixed(serial)) {
+        serial = (uint32_t)rand();
+    }
+    if(subghz_txrx_gen_data_protocol(
+           instance,
+           name_preset,
+           frequency,
+           SUBGHZ_PROTOCOL_SECPLUS_V1_NAME,
+           (uint64_t)serial << 32 | 0xE6000000,
+           42)) {
+        ret = true;
+    }
+    return ret;
+}

+ 96 - 0
applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h

@@ -0,0 +1,96 @@
+#pragma once
+#include "subghz_types.h"
+#include "subghz_txrx.h"
+
+/**
+ * Generate data for protocol
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param preset_name Name of preset
+ * @param frequency Frequency in Hz
+ * @param protocol_name Name of protocol
+ * @param key Key
+ * @param bit Bit
+ * @return bool True if success
+ */
+bool subghz_txrx_gen_data_protocol(
+    void* context,
+    const char* preset_name,
+    uint32_t frequency,
+    const char* protocol_name,
+    uint64_t key,
+    uint32_t bit);
+
+/**
+ * Generate data for protocol and te
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param preset_name Name of preset
+ * @param frequency Frequency in Hz
+ * @param protocol_name Name of protocol
+ * @param key Key
+ * @param bit Bit
+ * @param te Te
+ * @return bool True if success
+ */
+bool subghz_txrx_gen_data_protocol_and_te(
+    SubGhzTxRx* instance,
+    const char* preset_name,
+    uint32_t frequency,
+    const char* protocol_name,
+    uint64_t key,
+    uint32_t bit,
+    uint32_t te);
+
+/**
+ * Generate data Keeloq protocol
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param name_preset Name of preset
+ * @param frequency Frequency in Hz
+ * @param name_sysmem Name of Keeloq sysmem
+ * @param serial Serial number
+ * @param btn Button
+ * @param cnt Counter
+ * @return bool True if success
+ */
+bool subghz_txrx_gen_keeloq_protocol(
+    SubGhzTxRx* instance,
+    const char* name_preset,
+    uint32_t frequency,
+    const char* name_sysmem,
+    uint32_t serial,
+    uint8_t btn,
+    uint16_t cnt);
+
+/**
+ * Generate data SecPlus v2 protocol
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param name_preset Name of preset
+ * @param frequency Frequency in Hz
+ * @param serial Serial number
+ * @param btn Button
+ * @param cnt Counter
+ * @return bool True if success
+ */
+bool subghz_txrx_gen_secplus_v2_protocol(
+    SubGhzTxRx* instance,
+    const char* name_preset,
+    uint32_t frequency,
+    uint32_t serial,
+    uint8_t btn,
+    uint32_t cnt);
+
+/**
+ * Generate data SecPlus v1 protocol
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param name_preset Name of preset
+ * @param frequency Frequency in Hz
+ * @return bool True if success
+ */
+bool subghz_txrx_gen_secplus_v1_protocol(
+    SubGhzTxRx* instance,
+    const char* name_preset,
+    uint32_t frequency);

+ 27 - 0
applications/main/subghz/helpers/subghz_txrx_i.h

@@ -0,0 +1,27 @@
+#pragma once
+
+#include "subghz_txrx.h"
+
+struct SubGhzTxRx {
+    SubGhzWorker* worker;
+
+    SubGhzEnvironment* environment;
+    SubGhzReceiver* receiver;
+    SubGhzTransmitter* transmitter;
+    SubGhzProtocolDecoderBase* decoder_result;
+    FlipperFormat* fff_data;
+
+    SubGhzRadioPreset* preset;
+    SubGhzSetting* setting;
+
+    uint8_t hopper_timeout;
+    uint8_t hopper_idx_frequency;
+    bool is_database_loaded;
+    SubGhzHopperState hopper_state;
+
+    SubGhzTxRxState txrx_state;
+    SubGhzSpeakerState speaker_state;
+
+    SubGhzTxRxNeedSaveCallback need_save_callback;
+    void* need_save_context;
+};

+ 7 - 0
applications/main/subghz/helpers/subghz_types.h

@@ -77,3 +77,10 @@ typedef enum {
     SubGhzViewIdTestCarrier,
     SubGhzViewIdTestPacket,
 } SubGhzViewId;
+
+/** SubGhz load type file */
+typedef enum {
+    SubGhzLoadTypeFileNoLoad,
+    SubGhzLoadTypeFileKey,
+    SubGhzLoadTypeFileRaw,
+} SubGhzLoadTypeFile;

+ 2 - 2
applications/main/subghz/scenes/subghz_scene_delete.c

@@ -19,7 +19,7 @@ void subghz_scene_delete_on_enter(void* context) {
     modulation_str = furi_string_alloc();
     text = furi_string_alloc();
 
-    subghz_get_frequency_modulation(subghz, frequency_str, modulation_str);
+    subghz_txrx_get_frequency_and_modulation(subghz->txrx, frequency_str, modulation_str);
     widget_add_string_element(
         subghz->widget,
         78,
@@ -37,7 +37,7 @@ void subghz_scene_delete_on_enter(void* context) {
         AlignTop,
         FontSecondary,
         furi_string_get_cstr(modulation_str));
-    subghz_protocol_decoder_base_get_string(subghz->txrx->decoder_result, text);
+    subghz_protocol_decoder_base_get_string(subghz_txrx_get_decoder(subghz->txrx), text);
     widget_add_string_multiline_element(
         subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(text));
 

+ 1 - 1
applications/main/subghz/scenes/subghz_scene_delete_raw.c

@@ -33,7 +33,7 @@ void subghz_scene_delete_raw_on_enter(void* context) {
 
     widget_add_string_element(
         subghz->widget, 38, 25, AlignLeft, AlignTop, FontSecondary, "RAW signal");
-    subghz_get_frequency_modulation(subghz, frequency_str, modulation_str);
+    subghz_txrx_get_frequency_and_modulation(subghz->txrx, frequency_str, modulation_str);
     widget_add_string_element(
         subghz->widget,
         35,

+ 7 - 11
applications/main/subghz/scenes/subghz_scene_need_saving.c

@@ -37,27 +37,23 @@ void subghz_scene_need_saving_on_enter(void* context) {
 bool subghz_scene_need_saving_on_event(void* context, SceneManagerEvent event) {
     SubGhz* subghz = context;
     if(event.type == SceneManagerEventTypeBack) {
-        subghz->txrx->rx_key_state = SubGhzRxKeyStateBack;
+        subghz_rx_key_state_set(subghz, SubGhzRxKeyStateBack);
         scene_manager_previous_scene(subghz->scene_manager);
         return true;
     } else if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SubGhzCustomEventSceneStay) {
-            subghz->txrx->rx_key_state = SubGhzRxKeyStateBack;
+            subghz_rx_key_state_set(subghz, SubGhzRxKeyStateBack);
             scene_manager_previous_scene(subghz->scene_manager);
             return true;
         } else if(event.event == SubGhzCustomEventSceneExit) {
-            if(subghz->txrx->rx_key_state == SubGhzRxKeyStateExit) {
-                subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
-                subghz_preset_init(
-                    subghz,
-                    "AM650",
-                    subghz_setting_get_default_frequency(subghz->setting),
-                    NULL,
-                    0);
+            SubGhzRxKeyState state = subghz_rx_key_state_get(subghz);
+            subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE);
+
+            if(state == SubGhzRxKeyStateExit) {
+                subghz_set_default_preset(subghz);
                 scene_manager_search_and_switch_to_previous_scene(
                     subghz->scene_manager, SubGhzSceneStart);
             } else {
-                subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
                 scene_manager_previous_scene(subghz->scene_manager);
             }
 

+ 69 - 160
applications/main/subghz/scenes/subghz_scene_read_raw.c

@@ -3,11 +3,9 @@
 #include <dolphin/dolphin.h>
 #include <lib/subghz/protocols/raw.h>
 #include <lib/toolbox/path.h>
-#include <float_tools.h>
 
 #define RAW_FILE_NAME "Raw_signal_"
 #define TAG "SubGhzSceneReadRAW"
-#define RAW_THRESHOLD_RSSI_LOW_COUNT 10
 
 bool subghz_scene_read_raw_update_filename(SubGhz* subghz) {
     bool ret = false;
@@ -15,12 +13,13 @@ bool subghz_scene_read_raw_update_filename(SubGhz* subghz) {
     FuriString* temp_str;
     temp_str = furi_string_alloc();
     do {
-        if(!flipper_format_rewind(subghz->txrx->fff_data)) {
+        FlipperFormat* fff_data = subghz_txrx_get_fff_data(subghz->txrx);
+        if(!flipper_format_rewind(fff_data)) {
             FURI_LOG_E(TAG, "Rewind error");
             break;
         }
 
-        if(!flipper_format_read_string(subghz->txrx->fff_data, "File_name", temp_str)) {
+        if(!flipper_format_read_string(fff_data, "File_name", temp_str)) {
             FURI_LOG_E(TAG, "Missing File_name");
             break;
         }
@@ -38,13 +37,10 @@ static void subghz_scene_read_raw_update_statusbar(void* context) {
     furi_assert(context);
     SubGhz* subghz = context;
 
-    FuriString* frequency_str;
-    FuriString* modulation_str;
+    FuriString* frequency_str = furi_string_alloc();
+    FuriString* modulation_str = furi_string_alloc();
 
-    frequency_str = furi_string_alloc();
-    modulation_str = furi_string_alloc();
-
-    subghz_get_frequency_modulation(subghz, frequency_str, modulation_str);
+    subghz_txrx_get_frequency_and_modulation(subghz->txrx, frequency_str, modulation_str);
     subghz_read_raw_add_data_statusbar(
         subghz->subghz_read_raw,
         furi_string_get_cstr(frequency_str),
@@ -69,13 +65,13 @@ void subghz_scene_read_raw_callback_end_tx(void* context) {
 
 void subghz_scene_read_raw_on_enter(void* context) {
     SubGhz* subghz = context;
-    FuriString* file_name;
-    file_name = furi_string_alloc();
+    FuriString* file_name = furi_string_alloc();
 
-    switch(subghz->txrx->rx_key_state) {
+    float threshold_rssi = subghz_threshold_rssi_get(subghz->threshold_rssi);
+    switch(subghz_rx_key_state_get(subghz)) {
     case SubGhzRxKeyStateBack:
         subghz_read_raw_set_status(
-            subghz->subghz_read_raw, SubGhzReadRAWStatusIDLE, "", subghz->txrx->raw_threshold_rssi);
+            subghz->subghz_read_raw, SubGhzReadRAWStatusIDLE, "", threshold_rssi);
         break;
     case SubGhzRxKeyStateRAWLoad:
         path_extract_filename(subghz->file_path, file_name, true);
@@ -83,8 +79,7 @@ void subghz_scene_read_raw_on_enter(void* context) {
             subghz->subghz_read_raw,
             SubGhzReadRAWStatusLoadKeyTX,
             furi_string_get_cstr(file_name),
-            subghz->txrx->raw_threshold_rssi);
-        subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
+            threshold_rssi);
         break;
     case SubGhzRxKeyStateRAWSave:
         path_extract_filename(subghz->file_path, file_name, true);
@@ -92,66 +87,51 @@ void subghz_scene_read_raw_on_enter(void* context) {
             subghz->subghz_read_raw,
             SubGhzReadRAWStatusSaveKey,
             furi_string_get_cstr(file_name),
-            subghz->txrx->raw_threshold_rssi);
-        subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
+            threshold_rssi);
         break;
     default:
         subghz_read_raw_set_status(
-            subghz->subghz_read_raw,
-            SubGhzReadRAWStatusStart,
-            "",
-            subghz->txrx->raw_threshold_rssi);
-        subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
+            subghz->subghz_read_raw, SubGhzReadRAWStatusStart, "", threshold_rssi);
         break;
     }
+
+    if(subghz_rx_key_state_get(subghz) != SubGhzRxKeyStateBack) {
+        subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE);
+    }
     furi_string_free(file_name);
     subghz_scene_read_raw_update_statusbar(subghz);
 
     //set callback view raw
     subghz_read_raw_set_callback(subghz->subghz_read_raw, subghz_scene_read_raw_callback, subghz);
 
-    subghz->txrx->decoder_result = subghz_receiver_search_decoder_base_by_name(
-        subghz->txrx->receiver, SUBGHZ_PROTOCOL_RAW_NAME);
-    furi_assert(subghz->txrx->decoder_result);
+    furi_check(subghz_txrx_load_decoder_by_name_protocol(subghz->txrx, SUBGHZ_PROTOCOL_RAW_NAME));
 
     //set filter RAW feed
-    subghz_receiver_set_filter(subghz->txrx->receiver, SubGhzProtocolFlag_RAW);
+    subghz_txrx_receiver_set_filter(subghz->txrx, SubGhzProtocolFlag_RAW);
     view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdReadRAW);
 }
 
 bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
     SubGhz* subghz = context;
     bool consumed = false;
+    SubGhzProtocolDecoderRAW* decoder_raw =
+        (SubGhzProtocolDecoderRAW*)subghz_txrx_get_decoder(subghz->txrx);
     if(event.type == SceneManagerEventTypeCustom) {
         switch(event.event) {
         case SubGhzCustomEventViewReadRAWBack:
-            //Stop TX
-            if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) {
-                subghz_tx_stop(subghz);
-                subghz_sleep(subghz);
-            }
-            //Stop RX
-            if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
-                subghz_rx_end(subghz);
-                subghz_sleep(subghz);
-            };
+
+            subghz_txrx_stop(subghz->txrx);
             //Stop save file
-            subghz_protocol_raw_save_to_file_stop(
-                (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result);
+            subghz_protocol_raw_save_to_file_stop(decoder_raw);
             subghz->state_notifications = SubGhzNotificationStateIDLE;
             //needed save?
-            if((subghz->txrx->rx_key_state == SubGhzRxKeyStateAddKey) ||
-               (subghz->txrx->rx_key_state == SubGhzRxKeyStateBack)) {
-                subghz->txrx->rx_key_state = SubGhzRxKeyStateExit;
+            if((subghz_rx_key_state_get(subghz) == SubGhzRxKeyStateAddKey) ||
+               (subghz_rx_key_state_get(subghz) == SubGhzRxKeyStateBack)) {
+                subghz_rx_key_state_set(subghz, SubGhzRxKeyStateExit);
                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneNeedSaving);
             } else {
                 //Restore default setting
-                subghz_preset_init(
-                    subghz,
-                    "AM650",
-                    subghz_setting_get_default_frequency(subghz->setting),
-                    NULL,
-                    0);
+                subghz_set_default_preset(subghz);
                 if(!scene_manager_search_and_switch_to_previous_scene(
                        subghz->scene_manager, SubGhzSceneSaved)) {
                     if(!scene_manager_search_and_switch_to_previous_scene(
@@ -165,16 +145,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
             break;
 
         case SubGhzCustomEventViewReadRAWTXRXStop:
-            //Stop TX
-            if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) {
-                subghz_tx_stop(subghz);
-                subghz_sleep(subghz);
-            }
-            //Stop RX
-            if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
-                subghz_rx_end(subghz);
-                subghz_sleep(subghz);
-            };
+            subghz_txrx_stop(subghz->txrx);
             subghz->state_notifications = SubGhzNotificationStateIDLE;
             consumed = true;
             break;
@@ -187,13 +158,13 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
             break;
 
         case SubGhzCustomEventViewReadRAWErase:
-            if(subghz->txrx->rx_key_state == SubGhzRxKeyStateAddKey) {
+            if(subghz_rx_key_state_get(subghz) == SubGhzRxKeyStateAddKey) {
                 if(subghz_scene_read_raw_update_filename(subghz)) {
                     furi_string_set(subghz->file_path_tmp, subghz->file_path);
                     subghz_delete_file(subghz);
                 }
             }
-            subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
+            subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE);
             notification_message(subghz->notifications, &sequence_reset_rgb);
             consumed = true;
             break;
@@ -203,7 +174,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
                 if(subghz_scene_read_raw_update_filename(subghz)) {
                     scene_manager_set_scene_state(
                         subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerSet);
-                    subghz->txrx->rx_key_state = SubGhzRxKeyStateRAWLoad;
+                    subghz_rx_key_state_set(subghz, SubGhzRxKeyStateRAWLoad);
                     scene_manager_next_scene(subghz->scene_manager, SubGhzSceneMoreRAW);
                     consumed = true;
                 } else {
@@ -223,33 +194,22 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
             if(subghz_file_available(subghz) && subghz_scene_read_raw_update_filename(subghz)) {
                 //start send
                 subghz->state_notifications = SubGhzNotificationStateIDLE;
-                if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
-                    subghz_rx_end(subghz);
-                }
-                if((subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) ||
-                   (subghz->txrx->txrx_state == SubGhzTxRxStateSleep)) {
-                    if(!subghz_tx_start(subghz, subghz->txrx->fff_data)) {
-                        subghz->txrx->rx_key_state = SubGhzRxKeyStateBack;
-                        subghz_read_raw_set_status(
-                            subghz->subghz_read_raw,
-                            SubGhzReadRAWStatusIDLE,
-                            "",
-                            subghz->txrx->raw_threshold_rssi);
-                    } else {
-                        if(scene_manager_has_previous_scene(
-                               subghz->scene_manager, SubGhzSceneSaved) ||
-                           !scene_manager_has_previous_scene(
-                               subghz->scene_manager, SubGhzSceneStart)) {
-                            DOLPHIN_DEED(DolphinDeedSubGhzSend);
-                        }
-                        // set callback end tx
-                        subghz_protocol_raw_file_encoder_worker_set_callback_end(
-                            (SubGhzProtocolEncoderRAW*)subghz_transmitter_get_protocol_instance(
-                                subghz->txrx->transmitter),
-                            subghz_scene_read_raw_callback_end_tx,
-                            subghz);
-                        subghz->state_notifications = SubGhzNotificationStateTx;
+                if(!subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx))) {
+                    subghz_rx_key_state_set(subghz, SubGhzRxKeyStateBack);
+                    subghz_read_raw_set_status(
+                        subghz->subghz_read_raw,
+                        SubGhzReadRAWStatusIDLE,
+                        "",
+                        subghz_threshold_rssi_get(subghz->threshold_rssi));
+                } else {
+                    if(scene_manager_has_previous_scene(subghz->scene_manager, SubGhzSceneSaved) ||
+                       !scene_manager_has_previous_scene(subghz->scene_manager, SubGhzSceneStart)) {
+                        DOLPHIN_DEED(DolphinDeedSubGhzSend);
                     }
+                    // set callback end tx
+                    subghz_txrx_set_raw_file_encoder_worker_callback_end(
+                        subghz->txrx, subghz_scene_read_raw_callback_end_tx, subghz);
+                    subghz->state_notifications = SubGhzNotificationStateTx;
                 }
             } else {
                 if(!scene_manager_search_and_switch_to_previous_scene(
@@ -263,33 +223,22 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
 
         case SubGhzCustomEventViewReadRAWSendStop:
             subghz->state_notifications = SubGhzNotificationStateIDLE;
-            if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) {
-                subghz_speaker_unmute(subghz);
-                subghz_tx_stop(subghz);
-                subghz_sleep(subghz);
-            }
+            subghz_txrx_stop(subghz->txrx);
             subghz_read_raw_stop_send(subghz->subghz_read_raw);
             consumed = true;
             break;
 
         case SubGhzCustomEventViewReadRAWIDLE:
-            if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
-                subghz_rx_end(subghz);
-                subghz_sleep(subghz);
-            };
-
-            size_t spl_count = subghz_protocol_raw_get_sample_write(
-                (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result);
+            subghz_txrx_stop(subghz->txrx);
+            size_t spl_count = subghz_protocol_raw_get_sample_write(decoder_raw);
 
-            subghz_protocol_raw_save_to_file_stop(
-                (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result);
+            subghz_protocol_raw_save_to_file_stop(decoder_raw);
 
-            FuriString* temp_str;
-            temp_str = furi_string_alloc();
+            FuriString* temp_str = furi_string_alloc();
             furi_string_printf(
                 temp_str, "%s/%s%s", SUBGHZ_RAW_FOLDER, RAW_FILE_NAME, SUBGHZ_APP_EXTENSION);
             subghz_protocol_raw_gen_fff_data(
-                subghz->txrx->fff_data, furi_string_get_cstr(temp_str));
+                subghz_txrx_get_fff_data(subghz->txrx), furi_string_get_cstr(temp_str));
             furi_string_free(temp_str);
 
             if(spl_count > 0) {
@@ -299,32 +248,21 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
             }
 
             subghz->state_notifications = SubGhzNotificationStateIDLE;
-            subghz->txrx->rx_key_state = SubGhzRxKeyStateAddKey;
+            subghz_rx_key_state_set(subghz, SubGhzRxKeyStateAddKey);
 
             consumed = true;
             break;
 
         case SubGhzCustomEventViewReadRAWREC:
-            if(subghz->txrx->rx_key_state != SubGhzRxKeyStateIDLE) {
+            if(subghz_rx_key_state_get(subghz) != SubGhzRxKeyStateIDLE) {
                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneNeedSaving);
             } else {
-                subghz->txrx->raw_threshold_rssi_low_count = RAW_THRESHOLD_RSSI_LOW_COUNT;
-                if(subghz_protocol_raw_save_to_file_init(
-                       (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result,
-                       RAW_FILE_NAME,
-                       subghz->txrx->preset)) {
+                SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx);
+                if(subghz_protocol_raw_save_to_file_init(decoder_raw, RAW_FILE_NAME, &preset)) {
                     DOLPHIN_DEED(DolphinDeedSubGhzRawRec);
-                    if((subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) ||
-                       (subghz->txrx->txrx_state == SubGhzTxRxStateSleep)) {
-                        subghz_begin(
-                            subghz,
-                            subghz_setting_get_preset_data_by_name(
-                                subghz->setting,
-                                furi_string_get_cstr(subghz->txrx->preset->name)));
-                        subghz_rx(subghz, subghz->txrx->preset->frequency);
-                    }
+                    subghz_txrx_rx_start(subghz->txrx);
                     subghz->state_notifications = SubGhzNotificationStateRx;
-                    subghz->txrx->rx_key_state = SubGhzRxKeyStateAddKey;
+                    subghz_rx_key_state_set(subghz, SubGhzRxKeyStateAddKey);
                 } else {
                     furi_string_set(subghz->error_str, "Function requires\nan SD card.");
                     scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError);
@@ -337,7 +275,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
             if(subghz_file_available(subghz) && subghz_scene_read_raw_update_filename(subghz)) {
                 scene_manager_set_scene_state(
                     subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerSetRAW);
-                subghz->txrx->rx_key_state = SubGhzRxKeyStateBack;
+                subghz_rx_key_state_set(subghz, SubGhzRxKeyStateBack);
                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName);
             } else {
                 if(!scene_manager_search_and_switch_to_previous_scene(
@@ -356,41 +294,15 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
         switch(subghz->state_notifications) {
         case SubGhzNotificationStateRx:
             notification_message(subghz->notifications, &sequence_blink_cyan_10);
-            subghz_read_raw_update_sample_write(
-                subghz->subghz_read_raw,
-                subghz_protocol_raw_get_sample_write(
-                    (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result));
-
-            float rssi = furi_hal_subghz_get_rssi();
 
-            if(float_is_equal(subghz->txrx->raw_threshold_rssi, SUBGHZ_RAW_TRESHOLD_MIN)) {
-                subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, rssi, true);
-                subghz_protocol_raw_save_to_file_pause(
-                    (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result, false);
-            } else {
-                if(rssi < subghz->txrx->raw_threshold_rssi) {
-                    subghz->txrx->raw_threshold_rssi_low_count++;
-                    if(subghz->txrx->raw_threshold_rssi_low_count > RAW_THRESHOLD_RSSI_LOW_COUNT) {
-                        subghz->txrx->raw_threshold_rssi_low_count = RAW_THRESHOLD_RSSI_LOW_COUNT;
-                    }
-                    subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, rssi, false);
-                } else {
-                    subghz->txrx->raw_threshold_rssi_low_count = 0;
-                }
-
-                if(subghz->txrx->raw_threshold_rssi_low_count == RAW_THRESHOLD_RSSI_LOW_COUNT) {
-                    subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, rssi, false);
-                    subghz_protocol_raw_save_to_file_pause(
-                        (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result, true);
-                    subghz_speaker_mute(subghz);
-                } else {
-                    subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, rssi, true);
-                    subghz_protocol_raw_save_to_file_pause(
-                        (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result, false);
-                    subghz_speaker_unmute(subghz);
-                }
-            }
+            subghz_read_raw_update_sample_write(
+                subghz->subghz_read_raw, subghz_protocol_raw_get_sample_write(decoder_raw));
 
+            SubGhzThresholdRssiData ret_rssi =
+                subghz_threshold_get_rssi_data(subghz->threshold_rssi);
+            subghz_read_raw_add_data_rssi(
+                subghz->subghz_read_raw, ret_rssi.rssi, ret_rssi.is_above);
+            subghz_protocol_raw_save_to_file_pause(decoder_raw, !ret_rssi.is_above);
             break;
         case SubGhzNotificationStateTx:
             notification_message(subghz->notifications, &sequence_blink_magenta_10);
@@ -407,13 +319,10 @@ void subghz_scene_read_raw_on_exit(void* context) {
     SubGhz* subghz = context;
 
     //Stop CC1101
-    if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
-        subghz_rx_end(subghz);
-        subghz_sleep(subghz);
-    };
+    subghz_txrx_stop(subghz->txrx);
     subghz->state_notifications = SubGhzNotificationStateIDLE;
     notification_message(subghz->notifications, &sequence_reset_rgb);
 
     //filter restoration
-    subghz_receiver_set_filter(subghz->txrx->receiver, subghz->txrx->filter);
+    subghz_txrx_receiver_set_filter(subghz->txrx, subghz->filter);
 }

+ 48 - 74
applications/main/subghz/scenes/subghz_scene_receiver.c

@@ -35,16 +35,12 @@ static const NotificationSequence subghs_sequence_rx_locked = {
 
 static void subghz_scene_receiver_update_statusbar(void* context) {
     SubGhz* subghz = context;
-    FuriString* history_stat_str;
-    history_stat_str = furi_string_alloc();
-    if(!subghz_history_get_text_space_left(subghz->txrx->history, history_stat_str)) {
-        FuriString* frequency_str;
-        FuriString* modulation_str;
+    FuriString* history_stat_str = furi_string_alloc();
+    if(!subghz_history_get_text_space_left(subghz->history, history_stat_str)) {
+        FuriString* frequency_str = furi_string_alloc();
+        FuriString* modulation_str = furi_string_alloc();
 
-        frequency_str = furi_string_alloc();
-        modulation_str = furi_string_alloc();
-
-        subghz_get_frequency_modulation(subghz, frequency_str, modulation_str);
+        subghz_txrx_get_frequency_and_modulation(subghz->txrx, frequency_str, modulation_str);
 
         subghz_view_receiver_add_data_statusbar(
             subghz->subghz_receiver,
@@ -74,80 +70,68 @@ static void subghz_scene_add_to_history_callback(
     void* context) {
     furi_assert(context);
     SubGhz* subghz = context;
-    FuriString* str_buff;
-    str_buff = furi_string_alloc();
+    SubGhzHistory* history = subghz->history;
+    FuriString* str_buff = furi_string_alloc();
+
+    SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx);
 
-    if(subghz_history_add_to_history(subghz->txrx->history, decoder_base, subghz->txrx->preset)) {
+    if(subghz_history_add_to_history(history, decoder_base, &preset)) {
         furi_string_reset(str_buff);
 
         subghz->state_notifications = SubGhzNotificationStateRxDone;
-
-        subghz_history_get_text_item_menu(
-            subghz->txrx->history, str_buff, subghz_history_get_item(subghz->txrx->history) - 1);
+        uint16_t item_history = subghz_history_get_item(history);
+        subghz_history_get_text_item_menu(history, str_buff, item_history - 1);
         subghz_view_receiver_add_item_to_menu(
             subghz->subghz_receiver,
             furi_string_get_cstr(str_buff),
-            subghz_history_get_type_protocol(
-                subghz->txrx->history, subghz_history_get_item(subghz->txrx->history) - 1));
+            subghz_history_get_type_protocol(history, item_history - 1));
 
         subghz_scene_receiver_update_statusbar(subghz);
     }
     subghz_receiver_reset(receiver);
     furi_string_free(str_buff);
-    subghz->txrx->rx_key_state = SubGhzRxKeyStateAddKey;
+    subghz_rx_key_state_set(subghz, SubGhzRxKeyStateAddKey);
 }
 
 void subghz_scene_receiver_on_enter(void* context) {
     SubGhz* subghz = context;
+    SubGhzHistory* history = subghz->history;
 
     FuriString* str_buff;
     str_buff = furi_string_alloc();
 
-    if(subghz->txrx->rx_key_state == SubGhzRxKeyStateIDLE) {
-        subghz_preset_init(
-            subghz, "AM650", subghz_setting_get_default_frequency(subghz->setting), NULL, 0);
-        subghz_history_reset(subghz->txrx->history);
-        subghz->txrx->rx_key_state = SubGhzRxKeyStateStart;
+    if(subghz_rx_key_state_get(subghz) == SubGhzRxKeyStateIDLE) {
+        subghz_set_default_preset(subghz);
+        subghz_history_reset(history);
+        subghz_rx_key_state_set(subghz, SubGhzRxKeyStateStart);
     }
 
-    subghz_view_receiver_set_lock(subghz->subghz_receiver, subghz->lock);
+    subghz_view_receiver_set_lock(subghz->subghz_receiver, subghz_is_locked(subghz));
 
     //Load history to receiver
     subghz_view_receiver_exit(subghz->subghz_receiver);
-    for(uint8_t i = 0; i < subghz_history_get_item(subghz->txrx->history); i++) {
+    for(uint8_t i = 0; i < subghz_history_get_item(history); i++) {
         furi_string_reset(str_buff);
-        subghz_history_get_text_item_menu(subghz->txrx->history, str_buff, i);
+        subghz_history_get_text_item_menu(history, str_buff, i);
         subghz_view_receiver_add_item_to_menu(
             subghz->subghz_receiver,
             furi_string_get_cstr(str_buff),
-            subghz_history_get_type_protocol(subghz->txrx->history, i));
-        subghz->txrx->rx_key_state = SubGhzRxKeyStateAddKey;
+            subghz_history_get_type_protocol(history, i));
+        subghz_rx_key_state_set(subghz, SubGhzRxKeyStateAddKey);
     }
     furi_string_free(str_buff);
     subghz_scene_receiver_update_statusbar(subghz);
     subghz_view_receiver_set_callback(
         subghz->subghz_receiver, subghz_scene_receiver_callback, subghz);
-    subghz_receiver_set_rx_callback(
-        subghz->txrx->receiver, subghz_scene_add_to_history_callback, subghz);
+    subghz_txrx_set_rx_calback(subghz->txrx, subghz_scene_add_to_history_callback, subghz);
 
     subghz->state_notifications = SubGhzNotificationStateRx;
-    if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
-        subghz_rx_end(subghz);
-    };
-    if((subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) ||
-       (subghz->txrx->txrx_state == SubGhzTxRxStateSleep)) {
-        subghz_begin(
-            subghz,
-            subghz_setting_get_preset_data_by_name(
-                subghz->setting, furi_string_get_cstr(subghz->txrx->preset->name)));
-        subghz_rx(subghz, subghz->txrx->preset->frequency);
-    }
-    subghz_view_receiver_set_idx_menu(subghz->subghz_receiver, subghz->txrx->idx_menu_chosen);
+    subghz_txrx_rx_start(subghz->txrx);
+    subghz_view_receiver_set_idx_menu(subghz->subghz_receiver, subghz->idx_menu_chosen);
 
     //to use a universal decoder, we are looking for a link to it
-    subghz->txrx->decoder_result = subghz_receiver_search_decoder_base_by_name(
-        subghz->txrx->receiver, SUBGHZ_PROTOCOL_BIN_RAW_NAME);
-    furi_assert(subghz->txrx->decoder_result);
+    furi_check(
+        subghz_txrx_load_decoder_by_name_protocol(subghz->txrx, SUBGHZ_PROTOCOL_BIN_RAW_NAME));
 
     view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdReceiver);
 }
@@ -160,41 +144,31 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) {
         case SubGhzCustomEventViewReceiverBack:
             // Stop CC1101 Rx
             subghz->state_notifications = SubGhzNotificationStateIDLE;
-            if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
-                subghz_rx_end(subghz);
-                subghz_sleep(subghz);
-            };
-            subghz->txrx->hopper_state = SubGhzHopperStateOFF;
-            subghz->txrx->idx_menu_chosen = 0;
-            subghz_receiver_set_rx_callback(subghz->txrx->receiver, NULL, subghz);
-
-            if(subghz->txrx->rx_key_state == SubGhzRxKeyStateAddKey) {
-                subghz->txrx->rx_key_state = SubGhzRxKeyStateExit;
+            subghz_txrx_stop(subghz->txrx);
+            subghz_txrx_hopper_set_state(subghz->txrx, SubGhzHopperStateOFF);
+            subghz->idx_menu_chosen = 0;
+            subghz_txrx_set_rx_calback(subghz->txrx, NULL, subghz);
+
+            if(subghz_rx_key_state_get(subghz) == SubGhzRxKeyStateAddKey) {
+                subghz_rx_key_state_set(subghz, SubGhzRxKeyStateExit);
                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneNeedSaving);
             } else {
-                subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
-                subghz_preset_init(
-                    subghz,
-                    "AM650",
-                    subghz_setting_get_default_frequency(subghz->setting),
-                    NULL,
-                    0);
+                subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE);
+                subghz_set_default_preset(subghz);
                 scene_manager_search_and_switch_to_previous_scene(
                     subghz->scene_manager, SubGhzSceneStart);
             }
             consumed = true;
             break;
         case SubGhzCustomEventViewReceiverOK:
-            subghz->txrx->idx_menu_chosen =
-                subghz_view_receiver_get_idx_menu(subghz->subghz_receiver);
+            subghz->idx_menu_chosen = subghz_view_receiver_get_idx_menu(subghz->subghz_receiver);
             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverInfo);
             DOLPHIN_DEED(DolphinDeedSubGhzReceiverInfo);
             consumed = true;
             break;
         case SubGhzCustomEventViewReceiverConfig:
             subghz->state_notifications = SubGhzNotificationStateIDLE;
-            subghz->txrx->idx_menu_chosen =
-                subghz_view_receiver_get_idx_menu(subghz->subghz_receiver);
+            subghz->idx_menu_chosen = subghz_view_receiver_get_idx_menu(subghz->subghz_receiver);
             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverConfig);
             consumed = true;
             break;
@@ -203,30 +177,30 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) {
             consumed = true;
             break;
         case SubGhzCustomEventViewReceiverUnlock:
-            subghz->lock = SubGhzLockOff;
+            subghz_unlock(subghz);
             consumed = true;
             break;
         default:
             break;
         }
     } else if(event.type == SceneManagerEventTypeTick) {
-        if(subghz->txrx->hopper_state != SubGhzHopperStateOFF) {
-            subghz_hopper_update(subghz);
+        if(subghz_txrx_hopper_get_state(subghz->txrx) != SubGhzHopperStateOFF) {
+            subghz_txrx_hopper_update(subghz->txrx);
             subghz_scene_receiver_update_statusbar(subghz);
         }
 
-        //get RSSI
-        float rssi = furi_hal_subghz_get_rssi();
-        subghz_receiver_rssi(subghz->subghz_receiver, rssi);
+        SubGhzThresholdRssiData ret_rssi = subghz_threshold_get_rssi_data(subghz->threshold_rssi);
+
+        subghz_receiver_rssi(subghz->subghz_receiver, ret_rssi.rssi);
         subghz_protocol_decoder_bin_raw_data_input_rssi(
-            (SubGhzProtocolDecoderBinRAW*)subghz->txrx->decoder_result, rssi);
+            (SubGhzProtocolDecoderBinRAW*)subghz_txrx_get_decoder(subghz->txrx), ret_rssi.rssi);
 
         switch(subghz->state_notifications) {
         case SubGhzNotificationStateRx:
             notification_message(subghz->notifications, &sequence_blink_cyan_10);
             break;
         case SubGhzNotificationStateRxDone:
-            if(subghz->lock != SubGhzLockOn) {
+            if(!subghz_is_locked(subghz)) {
                 notification_message(subghz->notifications, &subghs_sequence_rx);
             } else {
                 notification_message(subghz->notifications, &subghs_sequence_rx_locked);

+ 78 - 55
applications/main/subghz/scenes/subghz_scene_receiver_config.c

@@ -72,13 +72,15 @@ const uint32_t bin_raw_value[BIN_RAW_COUNT] = {
 uint8_t subghz_scene_receiver_config_next_frequency(const uint32_t value, void* context) {
     furi_assert(context);
     SubGhz* subghz = context;
+    SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx);
+
     uint8_t index = 0;
-    for(uint8_t i = 0; i < subghz_setting_get_frequency_count(subghz->setting); i++) {
-        if(value == subghz_setting_get_frequency(subghz->setting, i)) {
+    for(uint8_t i = 0; i < subghz_setting_get_frequency_count(setting); i++) {
+        if(value == subghz_setting_get_frequency(setting, i)) {
             index = i;
             break;
         } else {
-            index = subghz_setting_get_frequency_default_index(subghz->setting);
+            index = subghz_setting_get_frequency_default_index(setting);
         }
     }
     return index;
@@ -87,13 +89,15 @@ uint8_t subghz_scene_receiver_config_next_frequency(const uint32_t value, void*
 uint8_t subghz_scene_receiver_config_next_preset(const char* preset_name, void* context) {
     furi_assert(context);
     SubGhz* subghz = context;
+    SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx);
+
     uint8_t index = 0;
-    for(uint8_t i = 0; i < subghz_setting_get_preset_count(subghz->setting); i++) {
-        if(!strcmp(subghz_setting_get_preset_name(subghz->setting, i), preset_name)) {
+    for(uint8_t i = 0; i < subghz_setting_get_preset_count(setting); i++) {
+        if(!strcmp(subghz_setting_get_preset_name(setting, i), preset_name)) {
             index = i;
             break;
         } else {
-            //  index = subghz_setting_get_frequency_default_index(subghz->setting);
+            //  index = subghz_setting_get_frequency_default_index(subghz_txrx_get_setting(subghz->txrx));
         }
     }
     return index;
@@ -122,70 +126,84 @@ uint8_t subghz_scene_receiver_config_hopper_value_index(
 static void subghz_scene_receiver_config_set_frequency(VariableItem* item) {
     SubGhz* subghz = variable_item_get_context(item);
     uint8_t index = variable_item_get_current_value_index(item);
+    SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx);
 
-    if(subghz->txrx->hopper_state == SubGhzHopperStateOFF) {
+    if(subghz_txrx_hopper_get_state(subghz->txrx) == SubGhzHopperStateOFF) {
         char text_buf[10] = {0};
+        uint32_t frequency = subghz_setting_get_frequency(setting, index);
+        SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx);
+
         snprintf(
             text_buf,
             sizeof(text_buf),
             "%lu.%02lu",
-            subghz_setting_get_frequency(subghz->setting, index) / 1000000,
-            (subghz_setting_get_frequency(subghz->setting, index) % 1000000) / 10000);
+            frequency / 1000000,
+            (frequency % 1000000) / 10000);
         variable_item_set_current_value_text(item, text_buf);
-        subghz->txrx->preset->frequency = subghz_setting_get_frequency(subghz->setting, index);
+        subghz_txrx_set_preset(
+            subghz->txrx,
+            furi_string_get_cstr(preset.name),
+            frequency,
+            preset.data,
+            preset.data_size);
     } else {
         variable_item_set_current_value_index(
-            item, subghz_setting_get_frequency_default_index(subghz->setting));
+            item, subghz_setting_get_frequency_default_index(setting));
     }
 }
 
 static void subghz_scene_receiver_config_set_preset(VariableItem* item) {
     SubGhz* subghz = variable_item_get_context(item);
     uint8_t index = variable_item_get_current_value_index(item);
-    variable_item_set_current_value_text(
-        item, subghz_setting_get_preset_name(subghz->setting, index));
-    subghz_preset_init(
-        subghz,
-        subghz_setting_get_preset_name(subghz->setting, index),
-        subghz->txrx->preset->frequency,
-        subghz_setting_get_preset_data(subghz->setting, index),
-        subghz_setting_get_preset_data_size(subghz->setting, index));
+    SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx);
+
+    variable_item_set_current_value_text(item, subghz_setting_get_preset_name(setting, index));
+
+    SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx);
+    subghz_txrx_set_preset(
+        subghz->txrx,
+        subghz_setting_get_preset_name(setting, index),
+        preset.frequency,
+        subghz_setting_get_preset_data(setting, index),
+        subghz_setting_get_preset_data_size(setting, index));
 }
 
 static void subghz_scene_receiver_config_set_hopping_running(VariableItem* item) {
     SubGhz* subghz = variable_item_get_context(item);
     uint8_t index = variable_item_get_current_value_index(item);
+    SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx);
+    VariableItem* frequency_item = (VariableItem*)scene_manager_get_scene_state(
+        subghz->scene_manager, SubGhzSceneReceiverConfig);
 
     variable_item_set_current_value_text(item, hopping_text[index]);
     if(hopping_value[index] == SubGhzHopperStateOFF) {
         char text_buf[10] = {0};
+        uint32_t frequency = subghz_setting_get_default_frequency(setting);
+        SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx);
+
         snprintf(
             text_buf,
             sizeof(text_buf),
             "%lu.%02lu",
-            subghz_setting_get_default_frequency(subghz->setting) / 1000000,
-            (subghz_setting_get_default_frequency(subghz->setting) % 1000000) / 10000);
-        variable_item_set_current_value_text(
-            (VariableItem*)scene_manager_get_scene_state(
-                subghz->scene_manager, SubGhzSceneReceiverConfig),
-            text_buf);
-        subghz->txrx->preset->frequency = subghz_setting_get_default_frequency(subghz->setting);
+            frequency / 1000000,
+            (frequency % 1000000) / 10000);
+        variable_item_set_current_value_text(frequency_item, text_buf);
+
+        subghz_txrx_set_preset(
+            subghz->txrx,
+            furi_string_get_cstr(preset.name),
+            frequency,
+            preset.data,
+            preset.data_size);
         variable_item_set_current_value_index(
-            (VariableItem*)scene_manager_get_scene_state(
-                subghz->scene_manager, SubGhzSceneReceiverConfig),
-            subghz_setting_get_frequency_default_index(subghz->setting));
+            frequency_item, subghz_setting_get_frequency_default_index(setting));
     } else {
-        variable_item_set_current_value_text(
-            (VariableItem*)scene_manager_get_scene_state(
-                subghz->scene_manager, SubGhzSceneReceiverConfig),
-            " -----");
+        variable_item_set_current_value_text(frequency_item, " -----");
         variable_item_set_current_value_index(
-            (VariableItem*)scene_manager_get_scene_state(
-                subghz->scene_manager, SubGhzSceneReceiverConfig),
-            subghz_setting_get_frequency_default_index(subghz->setting));
+            frequency_item, subghz_setting_get_frequency_default_index(setting));
     }
 
-    subghz->txrx->hopper_state = hopping_value[index];
+    subghz_txrx_hopper_set_state(subghz->txrx, hopping_value[index]);
 }
 
 static void subghz_scene_receiver_config_set_speaker(VariableItem* item) {
@@ -193,7 +211,7 @@ static void subghz_scene_receiver_config_set_speaker(VariableItem* item) {
     uint8_t index = variable_item_get_current_value_index(item);
 
     variable_item_set_current_value_text(item, speaker_text[index]);
-    subghz->txrx->speaker_state = speaker_value[index];
+    subghz_txrx_speaker_set_state(subghz->txrx, speaker_value[index]);
 }
 
 static void subghz_scene_receiver_config_set_bin_raw(VariableItem* item) {
@@ -201,8 +219,8 @@ static void subghz_scene_receiver_config_set_bin_raw(VariableItem* item) {
     uint8_t index = variable_item_get_current_value_index(item);
 
     variable_item_set_current_value_text(item, bin_raw_text[index]);
-    subghz->txrx->filter = bin_raw_value[index];
-    subghz_receiver_set_filter(subghz->txrx->receiver, subghz->txrx->filter);
+    subghz->filter = bin_raw_value[index];
+    subghz_txrx_receiver_set_filter(subghz->txrx, subghz->filter);
 }
 
 static void subghz_scene_receiver_config_set_raw_threshold_rssi(VariableItem* item) {
@@ -210,7 +228,7 @@ static void subghz_scene_receiver_config_set_raw_threshold_rssi(VariableItem* it
     uint8_t index = variable_item_get_current_value_index(item);
 
     variable_item_set_current_value_text(item, raw_theshold_rssi_text[index]);
-    subghz->txrx->raw_threshold_rssi = raw_theshold_rssi_value[index];
+    subghz_threshold_rssi_set(subghz->threshold_rssi, raw_theshold_rssi_value[index]);
 }
 
 static void subghz_scene_receiver_config_var_list_enter_callback(void* context, uint32_t index) {
@@ -226,25 +244,27 @@ void subghz_scene_receiver_config_on_enter(void* context) {
     SubGhz* subghz = context;
     VariableItem* item;
     uint8_t value_index;
+    SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx);
+    SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx);
 
     item = variable_item_list_add(
         subghz->variable_item_list,
         "Frequency:",
-        subghz_setting_get_frequency_count(subghz->setting),
+        subghz_setting_get_frequency_count(setting),
         subghz_scene_receiver_config_set_frequency,
         subghz);
-    value_index =
-        subghz_scene_receiver_config_next_frequency(subghz->txrx->preset->frequency, subghz);
+    value_index = subghz_scene_receiver_config_next_frequency(preset.frequency, subghz);
     scene_manager_set_scene_state(
         subghz->scene_manager, SubGhzSceneReceiverConfig, (uint32_t)item);
     variable_item_set_current_value_index(item, value_index);
     char text_buf[10] = {0};
+    uint32_t frequency = subghz_setting_get_frequency(setting, value_index);
     snprintf(
         text_buf,
         sizeof(text_buf),
         "%lu.%02lu",
-        subghz_setting_get_frequency(subghz->setting, value_index) / 1000000,
-        (subghz_setting_get_frequency(subghz->setting, value_index) % 1000000) / 10000);
+        frequency / 1000000,
+        (frequency % 1000000) / 10000);
     variable_item_set_current_value_text(item, text_buf);
 
     if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) !=
@@ -256,7 +276,7 @@ void subghz_scene_receiver_config_on_enter(void* context) {
             subghz_scene_receiver_config_set_hopping_running,
             subghz);
         value_index = subghz_scene_receiver_config_hopper_value_index(
-            subghz->txrx->hopper_state, hopping_value, HOPPING_COUNT, subghz);
+            subghz_txrx_hopper_get_state(subghz->txrx), hopping_value, HOPPING_COUNT, subghz);
         variable_item_set_current_value_index(item, value_index);
         variable_item_set_current_value_text(item, hopping_text[value_index]);
     }
@@ -264,14 +284,14 @@ void subghz_scene_receiver_config_on_enter(void* context) {
     item = variable_item_list_add(
         subghz->variable_item_list,
         "Modulation:",
-        subghz_setting_get_preset_count(subghz->setting),
+        subghz_setting_get_preset_count(setting),
         subghz_scene_receiver_config_set_preset,
         subghz);
-    value_index = subghz_scene_receiver_config_next_preset(
-        furi_string_get_cstr(subghz->txrx->preset->name), subghz);
+    value_index =
+        subghz_scene_receiver_config_next_preset(furi_string_get_cstr(preset.name), subghz);
     variable_item_set_current_value_index(item, value_index);
     variable_item_set_current_value_text(
-        item, subghz_setting_get_preset_name(subghz->setting, value_index));
+        item, subghz_setting_get_preset_name(setting, value_index));
 
     if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) !=
        SubGhzCustomEventManagerSet) {
@@ -281,7 +301,7 @@ void subghz_scene_receiver_config_on_enter(void* context) {
             BIN_RAW_COUNT,
             subghz_scene_receiver_config_set_bin_raw,
             subghz);
-        value_index = value_index_uint32(subghz->txrx->filter, bin_raw_value, BIN_RAW_COUNT);
+        value_index = value_index_uint32(subghz->filter, bin_raw_value, BIN_RAW_COUNT);
         variable_item_set_current_value_index(item, value_index);
         variable_item_set_current_value_text(item, bin_raw_text[value_index]);
     }
@@ -292,7 +312,8 @@ void subghz_scene_receiver_config_on_enter(void* context) {
         SPEAKER_COUNT,
         subghz_scene_receiver_config_set_speaker,
         subghz);
-    value_index = value_index_uint32(subghz->txrx->speaker_state, speaker_value, SPEAKER_COUNT);
+    value_index = value_index_uint32(
+        subghz_txrx_speaker_get_state(subghz->txrx), speaker_value, SPEAKER_COUNT);
     variable_item_set_current_value_index(item, value_index);
     variable_item_set_current_value_text(item, speaker_text[value_index]);
 
@@ -313,7 +334,9 @@ void subghz_scene_receiver_config_on_enter(void* context) {
             subghz_scene_receiver_config_set_raw_threshold_rssi,
             subghz);
         value_index = value_index_float(
-            subghz->txrx->raw_threshold_rssi, raw_theshold_rssi_value, RAW_THRESHOLD_RSSI_COUNT);
+            subghz_threshold_rssi_get(subghz->threshold_rssi),
+            raw_theshold_rssi_value,
+            RAW_THRESHOLD_RSSI_COUNT);
         variable_item_set_current_value_index(item, value_index);
         variable_item_set_current_value_text(item, raw_theshold_rssi_text[value_index]);
     }
@@ -326,7 +349,7 @@ bool subghz_scene_receiver_config_on_event(void* context, SceneManagerEvent even
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SubGhzCustomEventSceneSettingLock) {
-            subghz->lock = SubGhzLockOn;
+            subghz_lock(subghz);
             scene_manager_previous_scene(subghz->scene_manager);
             consumed = true;
         }

+ 35 - 77
applications/main/subghz/scenes/subghz_scene_receiver_info.c

@@ -19,20 +19,19 @@ void subghz_scene_receiver_info_callback(GuiButtonType result, InputType type, v
 
 static bool subghz_scene_receiver_info_update_parser(void* context) {
     SubGhz* subghz = context;
-    subghz->txrx->decoder_result = subghz_receiver_search_decoder_base_by_name(
-        subghz->txrx->receiver,
-        subghz_history_get_protocol_name(subghz->txrx->history, subghz->txrx->idx_menu_chosen));
 
-    if(subghz->txrx->decoder_result) {
+    if(subghz_txrx_load_decoder_by_name_protocol(
+           subghz->txrx,
+           subghz_history_get_protocol_name(subghz->history, subghz->idx_menu_chosen))) {
         //todo we are trying to deserialize without checking for errors, since it is assumed that we just received this chignal
         subghz_protocol_decoder_base_deserialize(
-            subghz->txrx->decoder_result,
-            subghz_history_get_raw_data(subghz->txrx->history, subghz->txrx->idx_menu_chosen));
+            subghz_txrx_get_decoder(subghz->txrx),
+            subghz_history_get_raw_data(subghz->history, subghz->idx_menu_chosen));
 
         SubGhzRadioPreset* preset =
-            subghz_history_get_radio_preset(subghz->txrx->history, subghz->txrx->idx_menu_chosen);
-        subghz_preset_init(
-            subghz,
+            subghz_history_get_radio_preset(subghz->history, subghz->idx_menu_chosen);
+        subghz_txrx_set_preset(
+            subghz->txrx,
             furi_string_get_cstr(preset->name),
             preset->frequency,
             preset->data,
@@ -47,15 +46,11 @@ void subghz_scene_receiver_info_on_enter(void* context) {
     SubGhz* subghz = context;
 
     if(subghz_scene_receiver_info_update_parser(subghz)) {
-        FuriString* frequency_str;
-        FuriString* modulation_str;
-        FuriString* text;
+        FuriString* frequency_str = furi_string_alloc();
+        FuriString* modulation_str = furi_string_alloc();
+        FuriString* text = furi_string_alloc();
 
-        frequency_str = furi_string_alloc();
-        modulation_str = furi_string_alloc();
-        text = furi_string_alloc();
-
-        subghz_get_frequency_modulation(subghz, frequency_str, modulation_str);
+        subghz_txrx_get_frequency_and_modulation(subghz->txrx, frequency_str, modulation_str);
         widget_add_string_element(
             subghz->widget,
             78,
@@ -73,7 +68,7 @@ void subghz_scene_receiver_info_on_enter(void* context) {
             AlignTop,
             FontSecondary,
             furi_string_get_cstr(modulation_str));
-        subghz_protocol_decoder_base_get_string(subghz->txrx->decoder_result, text);
+        subghz_protocol_decoder_base_get_string(subghz_txrx_get_decoder(subghz->txrx), text);
         widget_add_string_multiline_element(
             subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(text));
 
@@ -81,8 +76,7 @@ void subghz_scene_receiver_info_on_enter(void* context) {
         furi_string_free(modulation_str);
         furi_string_free(text);
 
-        if((subghz->txrx->decoder_result->protocol->flag & SubGhzProtocolFlag_Save) ==
-           SubGhzProtocolFlag_Save) {
+        if(subghz_txrx_protocol_is_serializable(subghz->txrx)) {
             widget_add_button_element(
                 subghz->widget,
                 GuiButtonTypeRight,
@@ -90,10 +84,7 @@ void subghz_scene_receiver_info_on_enter(void* context) {
                 subghz_scene_receiver_info_callback,
                 subghz);
         }
-        if(((subghz->txrx->decoder_result->protocol->flag & SubGhzProtocolFlag_Send) ==
-            SubGhzProtocolFlag_Send) &&
-           subghz->txrx->decoder_result->protocol->encoder->deserialize &&
-           subghz->txrx->decoder_result->protocol->type == SubGhzProtocolTypeStatic) {
+        if(subghz_txrx_protocol_is_transmittable(subghz->txrx, true)) {
             widget_add_button_element(
                 subghz->widget,
                 GuiButtonTypeCenter,
@@ -114,82 +105,49 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event)
     SubGhz* subghz = context;
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SubGhzCustomEventSceneReceiverInfoTxStart) {
-            //CC1101 Stop RX -> Start TX
-            if(subghz->txrx->hopper_state != SubGhzHopperStateOFF) {
-                subghz->txrx->hopper_state = SubGhzHopperStatePause;
-            }
-            if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
-                subghz_rx_end(subghz);
-            }
             if(!subghz_scene_receiver_info_update_parser(subghz)) {
                 return false;
             }
-            if(subghz->txrx->txrx_state == SubGhzTxRxStateIDLE ||
-               subghz->txrx->txrx_state == SubGhzTxRxStateSleep) {
-                if(!subghz_tx_start(
-                       subghz,
-                       subghz_history_get_raw_data(
-                           subghz->txrx->history, subghz->txrx->idx_menu_chosen))) {
-                    if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) {
-                        subghz_tx_stop(subghz);
-                    }
-                    if(subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) {
-                        subghz_begin(
-                            subghz,
-                            subghz_setting_get_preset_data_by_name(
-                                subghz->setting,
-                                furi_string_get_cstr(subghz->txrx->preset->name)));
-                        subghz_rx(subghz, subghz->txrx->preset->frequency);
-                    }
-                    if(subghz->txrx->hopper_state == SubGhzHopperStatePause) {
-                        subghz->txrx->hopper_state = SubGhzHopperStateRunnig;
-                    }
-                    subghz->state_notifications = SubGhzNotificationStateRx;
-                } else {
-                    subghz->state_notifications = SubGhzNotificationStateTx;
-                }
+            //CC1101 Stop RX -> Start TX
+            subghz_txrx_hopper_pause(subghz->txrx);
+            if(!subghz_tx_start(
+                   subghz,
+                   subghz_history_get_raw_data(subghz->history, subghz->idx_menu_chosen))) {
+                subghz_txrx_rx_start(subghz->txrx);
+                subghz_txrx_hopper_unpause(subghz->txrx);
+                subghz->state_notifications = SubGhzNotificationStateRx;
+            } else {
+                subghz->state_notifications = SubGhzNotificationStateTx;
             }
             return true;
         } else if(event.event == SubGhzCustomEventSceneReceiverInfoTxStop) {
             //CC1101 Stop Tx -> Start RX
             subghz->state_notifications = SubGhzNotificationStateIDLE;
-            if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) {
-                subghz_tx_stop(subghz);
-            }
-            if(subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) {
-                subghz_begin(
-                    subghz,
-                    subghz_setting_get_preset_data_by_name(
-                        subghz->setting, furi_string_get_cstr(subghz->txrx->preset->name)));
-                subghz_rx(subghz, subghz->txrx->preset->frequency);
-            }
-            if(subghz->txrx->hopper_state == SubGhzHopperStatePause) {
-                subghz->txrx->hopper_state = SubGhzHopperStateRunnig;
-            }
+
+            subghz_txrx_rx_start(subghz->txrx);
+
+            subghz_txrx_hopper_unpause(subghz->txrx);
             subghz->state_notifications = SubGhzNotificationStateRx;
             return true;
         } else if(event.event == SubGhzCustomEventSceneReceiverInfoSave) {
             //CC1101 Stop RX -> Save
             subghz->state_notifications = SubGhzNotificationStateIDLE;
-            subghz->txrx->hopper_state = SubGhzHopperStateOFF;
-            if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
-                subghz_rx_end(subghz);
-                subghz_sleep(subghz);
-            }
+            subghz_txrx_hopper_set_state(subghz->txrx, SubGhzHopperStateOFF);
+
+            subghz_txrx_stop(subghz->txrx);
             if(!subghz_scene_receiver_info_update_parser(subghz)) {
                 return false;
             }
 
-            if((subghz->txrx->decoder_result->protocol->flag & SubGhzProtocolFlag_Save) ==
-               SubGhzProtocolFlag_Save) {
+            if(subghz_txrx_protocol_is_serializable(subghz->txrx)) {
                 subghz_file_name_clear(subghz);
                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName);
             }
             return true;
         }
     } else if(event.type == SceneManagerEventTypeTick) {
-        if(subghz->txrx->hopper_state != SubGhzHopperStateOFF) {
-            subghz_hopper_update(subghz);
+        if(subghz_txrx_hopper_get_state(subghz->txrx) != SubGhzHopperStateOFF) {
+            subghz_txrx_hopper_update(subghz->txrx);
         }
         switch(subghz->state_notifications) {
         case SubGhzNotificationStateTx:

+ 1 - 2
applications/main/subghz/scenes/subghz_scene_region_info.c

@@ -5,8 +5,7 @@
 void subghz_scene_region_info_on_enter(void* context) {
     SubGhz* subghz = context;
     const FuriHalRegion* const region = furi_hal_region_get();
-    FuriString* buffer;
-    buffer = furi_string_alloc();
+    FuriString* buffer = furi_string_alloc();
     if(region) {
         furi_string_cat_printf(buffer, "Region: %s,  bands:\n", region->country_code);
         for(uint16_t i = 0; i < region->bands_count; ++i) {

+ 10 - 10
applications/main/subghz/scenes/subghz_scene_rpc.c

@@ -3,6 +3,7 @@
 typedef enum {
     SubGhzRpcStateIdle,
     SubGhzRpcStateLoaded,
+    SubGhzRpcStateTx,
 } SubGhzRpcState;
 
 void subghz_scene_rpc_on_enter(void* context) {
@@ -38,9 +39,9 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) {
             view_dispatcher_stop(subghz->view_dispatcher);
         } else if(event.event == SubGhzCustomEventSceneRpcButtonPress) {
             bool result = false;
-            if((subghz->txrx->txrx_state == SubGhzTxRxStateSleep) &&
-               (state == SubGhzRpcStateLoaded)) {
-                result = subghz_tx_start(subghz, subghz->txrx->fff_data);
+            if((state == SubGhzRpcStateLoaded)) {
+                result = subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx));
+                state = SubGhzRpcStateTx;
                 if(result) subghz_blink_start(subghz);
             }
             if(!result) {
@@ -52,10 +53,10 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) {
             rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventButtonPress, result);
         } else if(event.event == SubGhzCustomEventSceneRpcButtonRelease) {
             bool result = false;
-            if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) {
+            if(state == SubGhzRpcStateTx) {
+                subghz_txrx_stop(subghz->txrx);
                 subghz_blink_stop(subghz);
-                subghz_tx_stop(subghz);
-                subghz_sleep(subghz);
+                state = SubGhzRpcStateIdle;
                 result = true;
             }
             rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventButtonRelease, result);
@@ -93,10 +94,9 @@ 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);
+    SubGhzRpcState state = scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneRpc);
+    if(state != SubGhzRpcStateIdle) {
+        subghz_txrx_stop(subghz->txrx);
         subghz_blink_stop(subghz);
     }
 

+ 7 - 9
applications/main/subghz/scenes/subghz_scene_save_name.c

@@ -35,10 +35,8 @@ void subghz_scene_save_name_on_enter(void* context) {
     TextInput* text_input = subghz->text_input;
     bool dev_name_empty = false;
 
-    FuriString* file_name;
-    FuriString* dir_name;
-    file_name = furi_string_alloc();
-    dir_name = furi_string_alloc();
+    FuriString* file_name = furi_string_alloc();
+    FuriString* dir_name = furi_string_alloc();
 
     if(!subghz_path_is_file(subghz->file_path)) {
         char file_name_buf[SUBGHZ_MAX_LEN_NAME] = {0};
@@ -69,7 +67,7 @@ void subghz_scene_save_name_on_enter(void* context) {
         subghz_scene_save_name_text_input_callback,
         subghz,
         subghz->file_name_tmp,
-        MAX_TEXT_INPUT_LEN, // buffer size
+        MAX_TEXT_INPUT_LEN,
         dev_name_empty);
 
     ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
@@ -106,7 +104,7 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) {
                        SubGhzCustomEventManagerNoSet) {
                         subghz_save_protocol_to_file(
                             subghz,
-                            subghz->txrx->fff_data,
+                            subghz_txrx_get_fff_data(subghz->txrx),
                             furi_string_get_cstr(subghz->file_path));
                         scene_manager_set_scene_state(
                             subghz->scene_manager,
@@ -115,8 +113,7 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) {
                     } else {
                         subghz_save_protocol_to_file(
                             subghz,
-                            subghz_history_get_raw_data(
-                                subghz->txrx->history, subghz->txrx->idx_menu_chosen),
+                            subghz_history_get_raw_data(subghz->history, subghz->idx_menu_chosen),
                             furi_string_get_cstr(subghz->file_path));
                     }
                 }
@@ -124,7 +121,8 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) {
                 if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) !=
                    SubGhzCustomEventManagerNoSet) {
                     subghz_protocol_raw_gen_fff_data(
-                        subghz->txrx->fff_data, furi_string_get_cstr(subghz->file_path));
+                        subghz_txrx_get_fff_data(subghz->txrx),
+                        furi_string_get_cstr(subghz->file_path));
                     scene_manager_set_scene_state(
                         subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerNoSet);
                 } else {

+ 2 - 2
applications/main/subghz/scenes/subghz_scene_save_success.c

@@ -26,10 +26,10 @@ bool subghz_scene_save_success_on_event(void* context, SceneManagerEvent event)
         if(event.event == SubGhzCustomEventSceneSaveSuccess) {
             if(!scene_manager_search_and_switch_to_previous_scene(
                    subghz->scene_manager, SubGhzSceneReceiver)) {
-                subghz->txrx->rx_key_state = SubGhzRxKeyStateRAWSave;
+                subghz_rx_key_state_set(subghz, SubGhzRxKeyStateRAWSave);
                 if(!scene_manager_search_and_switch_to_previous_scene(
                        subghz->scene_manager, SubGhzSceneReadRAW)) {
-                    subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
+                    subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE);
                     if(!scene_manager_search_and_switch_to_previous_scene(
                            subghz->scene_manager, SubGhzSceneSaved)) {
                         scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaved);

+ 2 - 2
applications/main/subghz/scenes/subghz_scene_saved.c

@@ -4,8 +4,8 @@ void subghz_scene_saved_on_enter(void* context) {
     SubGhz* subghz = context;
 
     if(subghz_load_protocol_from_file(subghz)) {
-        if((!strcmp(subghz->txrx->decoder_result->protocol->name, "RAW"))) {
-            subghz->txrx->rx_key_state = SubGhzRxKeyStateRAWLoad;
+        if(subghz_get_load_type_file(subghz) == SubGhzLoadTypeFileRaw) {
+            subghz_rx_key_state_set(subghz, SubGhzRxKeyStateRAWLoad);
             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReadRAW);
         } else {
             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSavedMenu);

+ 46 - 209
applications/main/subghz/scenes/subghz_scene_set_type.c

@@ -1,63 +1,10 @@
 #include "../subghz_i.h"
-#include <lib/subghz/protocols/keeloq.h>
-#include <lib/subghz/protocols/secplus_v1.h>
-#include <lib/subghz/protocols/secplus_v2.h>
+#include "../helpers/subghz_txrx_create_protocol_key.h"
 #include <lib/subghz/blocks/math.h>
-#include <flipper_format/flipper_format_i.h>
-#include <lib/toolbox/stream/stream.h>
 #include <lib/subghz/protocols/protocol_items.h>
 
 #define TAG "SubGhzSetType"
 
-bool subghz_scene_set_type_submenu_gen_data_protocol(
-    void* context,
-    const char* protocol_name,
-    uint64_t key,
-    uint32_t bit,
-    uint32_t frequency,
-    const char* preset_name) {
-    furi_assert(context);
-    SubGhz* subghz = context;
-
-    bool res = false;
-
-    subghz_preset_init(subghz, preset_name, frequency, NULL, 0);
-    subghz->txrx->decoder_result =
-        subghz_receiver_search_decoder_base_by_name(subghz->txrx->receiver, protocol_name);
-
-    if(subghz->txrx->decoder_result == NULL) {
-        furi_string_set(subghz->error_str, "Protocol not\nfound!");
-        scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub);
-        return false;
-    }
-
-    do {
-        Stream* fff_data_stream = flipper_format_get_raw_stream(subghz->txrx->fff_data);
-        stream_clean(fff_data_stream);
-        if(subghz_protocol_decoder_base_serialize(
-               subghz->txrx->decoder_result, subghz->txrx->fff_data, subghz->txrx->preset) !=
-           SubGhzProtocolStatusOk) {
-            FURI_LOG_E(TAG, "Unable to serialize");
-            break;
-        }
-        if(!flipper_format_update_uint32(subghz->txrx->fff_data, "Bit", &bit, 1)) {
-            FURI_LOG_E(TAG, "Unable to update Bit");
-            break;
-        }
-
-        uint8_t key_data[sizeof(uint64_t)] = {0};
-        for(size_t i = 0; i < sizeof(uint64_t); i++) {
-            key_data[sizeof(uint64_t) - i - 1] = (key >> (i * 8)) & 0xFF;
-        }
-        if(!flipper_format_update_hex(subghz->txrx->fff_data, "Key", key_data, sizeof(uint64_t))) {
-            FURI_LOG_E(TAG, "Unable to update Key");
-            break;
-        }
-        res = true;
-    } while(false);
-    return res;
-}
-
 void subghz_scene_set_type_submenu_callback(void* context, uint32_t index) {
     SubGhz* subghz = context;
     view_dispatcher_send_custom_event(subghz->view_dispatcher, index);
@@ -69,7 +16,13 @@ void subghz_scene_set_type_on_enter(void* context) {
     submenu_add_item(
         subghz->submenu,
         "Princeton_433",
-        SubmenuIndexPricenton,
+        SubmenuIndexPricenton_433,
+        subghz_scene_set_type_submenu_callback,
+        subghz);
+    submenu_add_item(
+        subghz->submenu,
+        "Princeton_315",
+        SubmenuIndexPricenton_315,
         subghz_scene_set_type_submenu_callback,
         subghz);
     submenu_add_item(
@@ -108,10 +61,6 @@ void subghz_scene_set_type_on_enter(void* context) {
         SubmenuIndexCAMETwee,
         subghz_scene_set_type_submenu_callback,
         subghz);
-    // submenu_add_item(
-    //     subghz->submenu, "Nero Sketch", SubmenuIndexNeroSketch, subghz_scene_set_type_submenu_callback, subghz);
-    // submenu_add_item(
-    //     subghz->submenu, "Nero Radio", SubmenuIndexNeroRadio, subghz_scene_set_type_submenu_callback, subghz);
     submenu_add_item(
         subghz->submenu,
         "Gate TX_433",
@@ -172,94 +121,59 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) {
     bool generated_protocol = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
-        //ToDo Fix
-        uint32_t key = subghz_random_serial();
+        uint32_t key = (uint32_t)rand();
         switch(event.event) {
-        case SubmenuIndexPricenton:
+        case SubmenuIndexPricenton_433:
             key = (key & 0x00FFFFF0) | 0x4; //btn 0x1, 0x2, 0x4, 0x8
-            if(subghz_scene_set_type_submenu_gen_data_protocol(
-                   subghz, SUBGHZ_PROTOCOL_PRINCETON_NAME, key, 24, 433920000, "AM650")) {
-                uint32_t te = 400;
-                flipper_format_update_uint32(subghz->txrx->fff_data, "TE", (uint32_t*)&te, 1);
-                generated_protocol = true;
-            }
+            generated_protocol = subghz_txrx_gen_data_protocol_and_te(
+                subghz->txrx, "AM650", 433920000, SUBGHZ_PROTOCOL_PRINCETON_NAME, key, 24, 400);
+            break;
+        case SubmenuIndexPricenton_315:
+            key = (key & 0x00FFFFF0) | 0x4; //btn 0x1, 0x2, 0x4, 0x8
+            generated_protocol = subghz_txrx_gen_data_protocol_and_te(
+                subghz->txrx, "AM650", 315000000, SUBGHZ_PROTOCOL_PRINCETON_NAME, key, 24, 400);
             break;
         case SubmenuIndexNiceFlo12bit:
             key = (key & 0x0000FFF0) | 0x1; //btn 0x1, 0x2, 0x4
-            if(subghz_scene_set_type_submenu_gen_data_protocol(
-                   subghz, SUBGHZ_PROTOCOL_NICE_FLO_NAME, key, 12, 433920000, "AM650")) {
-                generated_protocol = true;
-            }
+            generated_protocol = subghz_txrx_gen_data_protocol(
+                subghz->txrx, "AM650", 433920000, SUBGHZ_PROTOCOL_NICE_FLO_NAME, key, 12);
             break;
         case SubmenuIndexNiceFlo24bit:
             key = (key & 0x00FFFFF0) | 0x4; //btn 0x1, 0x2, 0x4, 0x8
-            if(subghz_scene_set_type_submenu_gen_data_protocol(
-                   subghz, SUBGHZ_PROTOCOL_NICE_FLO_NAME, key, 24, 433920000, "AM650")) {
-                generated_protocol = true;
-            }
+            generated_protocol = subghz_txrx_gen_data_protocol(
+                subghz->txrx, "AM650", 433920000, SUBGHZ_PROTOCOL_NICE_FLO_NAME, key, 24);
             break;
         case SubmenuIndexCAME12bit:
             key = (key & 0x0000FFF0) | 0x1; //btn 0x1, 0x2, 0x4
-            if(subghz_scene_set_type_submenu_gen_data_protocol(
-                   subghz, SUBGHZ_PROTOCOL_CAME_NAME, key, 12, 433920000, "AM650")) {
-                generated_protocol = true;
-            }
+            generated_protocol = subghz_txrx_gen_data_protocol(
+                subghz->txrx, "AM650", 433920000, SUBGHZ_PROTOCOL_CAME_NAME, key, 12);
             break;
         case SubmenuIndexCAME24bit:
             key = (key & 0x00FFFFF0) | 0x4; //btn 0x1, 0x2, 0x4, 0x8
-            if(subghz_scene_set_type_submenu_gen_data_protocol(
-                   subghz, SUBGHZ_PROTOCOL_CAME_NAME, key, 24, 433920000, "AM650")) {
-                generated_protocol = true;
-            }
+            generated_protocol = subghz_txrx_gen_data_protocol(
+                subghz->txrx, "AM650", 433920000, SUBGHZ_PROTOCOL_CAME_NAME, key, 24);
             break;
         case SubmenuIndexLinear_300_00:
             key = (key & 0x3FF);
-            if(subghz_scene_set_type_submenu_gen_data_protocol(
-                   subghz, SUBGHZ_PROTOCOL_LINEAR_NAME, key, 10, 300000000, "AM650")) {
-                generated_protocol = true;
-            }
+            generated_protocol = subghz_txrx_gen_data_protocol(
+                subghz->txrx, "AM650", 300000000, SUBGHZ_PROTOCOL_LINEAR_NAME, key, 10);
             break;
         case SubmenuIndexCAMETwee:
             key = (key & 0x0FFFFFF0);
             key = 0x003FFF7200000000 | (key ^ 0xE0E0E0EE);
-            if(subghz_scene_set_type_submenu_gen_data_protocol(
-                   subghz, SUBGHZ_PROTOCOL_CAME_TWEE_NAME, key, 54, 433920000, "AM650")) {
-                generated_protocol = true;
-            }
+
+            generated_protocol = subghz_txrx_gen_data_protocol(
+                subghz->txrx, "AM650", 433920000, SUBGHZ_PROTOCOL_CAME_TWEE_NAME, key, 54);
             break;
-        // case SubmenuIndexNeroSketch:
-        //     /* code */
-        //     break;
-        // case SubmenuIndexNeroRadio:
-        //     /* code */
-        //     break;
         case SubmenuIndexGateTX:
             key = (key & 0x00F0FF00) | 0xF << 16 | 0x40; //btn 0xF, 0xC, 0xA, 0x6 (?)
             uint64_t rev_key = subghz_protocol_blocks_reverse_key(key, 24);
-            if(subghz_scene_set_type_submenu_gen_data_protocol(
-                   subghz, SUBGHZ_PROTOCOL_GATE_TX_NAME, rev_key, 24, 433920000, "AM650")) {
-                generated_protocol = true;
-            }
+            generated_protocol = subghz_txrx_gen_data_protocol(
+                subghz->txrx, "AM650", 433920000, SUBGHZ_PROTOCOL_GATE_TX_NAME, rev_key, 24);
             break;
         case SubmenuIndexDoorHan_433_92:
-            subghz->txrx->transmitter = subghz_transmitter_alloc_init(
-                subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME);
-            subghz_preset_init(
-                subghz, "AM650", subghz_setting_get_default_frequency(subghz->setting), NULL, 0);
-            if(subghz->txrx->transmitter) {
-                subghz_protocol_keeloq_create_data(
-                    subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter),
-                    subghz->txrx->fff_data,
-                    key & 0x0FFFFFFF,
-                    0x2,
-                    0x0003,
-                    "DoorHan",
-                    subghz->txrx->preset);
-                generated_protocol = true;
-            } else {
-                generated_protocol = false;
-            }
-            subghz_transmitter_free(subghz->txrx->transmitter);
+            generated_protocol = subghz_txrx_gen_keeloq_protocol(
+                subghz->txrx, "AM650", 433920000, "DoorHan", key, 0x2, 0x0003);
             if(!generated_protocol) {
                 furi_string_set(
                     subghz->error_str, "Function requires\nan SD card with\nfresh databases.");
@@ -267,23 +181,8 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) {
             }
             break;
         case SubmenuIndexDoorHan_315_00:
-            subghz->txrx->transmitter = subghz_transmitter_alloc_init(
-                subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME);
-            subghz_preset_init(subghz, "AM650", 315000000, NULL, 0);
-            if(subghz->txrx->transmitter) {
-                subghz_protocol_keeloq_create_data(
-                    subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter),
-                    subghz->txrx->fff_data,
-                    key & 0x0FFFFFFF,
-                    0x2,
-                    0x0003,
-                    "DoorHan",
-                    subghz->txrx->preset);
-                generated_protocol = true;
-            } else {
-                generated_protocol = false;
-            }
-            subghz_transmitter_free(subghz->txrx->transmitter);
+            generated_protocol = subghz_txrx_gen_keeloq_protocol(
+                subghz->txrx, "AM650", 315000000, "DoorHan", key, 0x2, 0x0003);
             if(!generated_protocol) {
                 furi_string_set(
                     subghz->error_str, "Function requires\nan SD card with\nfresh databases.");
@@ -291,86 +190,24 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) {
             }
             break;
         case SubmenuIndexLiftMaster_315_00:
-            while(!subghz_protocol_secplus_v1_check_fixed(key)) {
-                key = subghz_random_serial();
-            }
-            if(subghz_scene_set_type_submenu_gen_data_protocol(
-                   subghz,
-                   SUBGHZ_PROTOCOL_SECPLUS_V1_NAME,
-                   (uint64_t)key << 32 | 0xE6000000,
-                   42,
-                   315000000,
-                   "AM650")) {
-                generated_protocol = true;
-            }
+            generated_protocol =
+                subghz_txrx_gen_secplus_v1_protocol(subghz->txrx, "AM650", 315000000);
             break;
         case SubmenuIndexLiftMaster_390_00:
-            while(!subghz_protocol_secplus_v1_check_fixed(key)) {
-                key = subghz_random_serial();
-            }
-            if(subghz_scene_set_type_submenu_gen_data_protocol(
-                   subghz,
-                   SUBGHZ_PROTOCOL_SECPLUS_V1_NAME,
-                   (uint64_t)key << 32 | 0xE6000000,
-                   42,
-                   390000000,
-                   "AM650")) {
-                generated_protocol = true;
-            }
+            generated_protocol =
+                subghz_txrx_gen_secplus_v1_protocol(subghz->txrx, "AM650", 390000000);
             break;
         case SubmenuIndexSecPlus_v2_310_00:
-            subghz->txrx->transmitter = subghz_transmitter_alloc_init(
-                subghz->txrx->environment, SUBGHZ_PROTOCOL_SECPLUS_V2_NAME);
-            subghz_preset_init(subghz, "AM650", 310000000, NULL, 0);
-            if(subghz->txrx->transmitter) {
-                subghz_protocol_secplus_v2_create_data(
-                    subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter),
-                    subghz->txrx->fff_data,
-                    key,
-                    0x68,
-                    0xE500000,
-                    subghz->txrx->preset);
-                generated_protocol = true;
-            } else {
-                generated_protocol = false;
-            }
-            subghz_transmitter_free(subghz->txrx->transmitter);
+            generated_protocol = subghz_txrx_gen_secplus_v2_protocol(
+                subghz->txrx, "AM650", 310000000, key, 0x68, 0xE500000);
             break;
         case SubmenuIndexSecPlus_v2_315_00:
-            subghz->txrx->transmitter = subghz_transmitter_alloc_init(
-                subghz->txrx->environment, SUBGHZ_PROTOCOL_SECPLUS_V2_NAME);
-            subghz_preset_init(subghz, "AM650", 315000000, NULL, 0);
-            if(subghz->txrx->transmitter) {
-                subghz_protocol_secplus_v2_create_data(
-                    subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter),
-                    subghz->txrx->fff_data,
-                    key,
-                    0x68,
-                    0xE500000,
-                    subghz->txrx->preset);
-                generated_protocol = true;
-            } else {
-                generated_protocol = false;
-            }
-            subghz_transmitter_free(subghz->txrx->transmitter);
+            generated_protocol = subghz_txrx_gen_secplus_v2_protocol(
+                subghz->txrx, "AM650", 315000000, key, 0x68, 0xE500000);
             break;
         case SubmenuIndexSecPlus_v2_390_00:
-            subghz->txrx->transmitter = subghz_transmitter_alloc_init(
-                subghz->txrx->environment, SUBGHZ_PROTOCOL_SECPLUS_V2_NAME);
-            subghz_preset_init(subghz, "AM650", 390000000, NULL, 0);
-            if(subghz->txrx->transmitter) {
-                subghz_protocol_secplus_v2_create_data(
-                    subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter),
-                    subghz->txrx->fff_data,
-                    key,
-                    0x68,
-                    0xE500000,
-                    subghz->txrx->preset);
-                generated_protocol = true;
-            } else {
-                generated_protocol = false;
-            }
-            subghz_transmitter_free(subghz->txrx->transmitter);
+            generated_protocol = subghz_txrx_gen_secplus_v2_protocol(
+                subghz->txrx, "AM650", 390000000, key, 0x68, 0xE500000);
             break;
         default:
             return false;

+ 5 - 6
applications/main/subghz/scenes/subghz_scene_show_error.c

@@ -50,9 +50,10 @@ void subghz_scene_show_error_on_enter(void* context) {
 
 bool subghz_scene_show_error_on_event(void* context, SceneManagerEvent event) {
     SubGhz* subghz = context;
+    SubGhzCustomEvent scene_state =
+        scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneShowError);
     if(event.type == SceneManagerEventTypeBack) {
-        if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneShowError) ==
-           SubGhzCustomEventManagerSet) {
+        if(scene_state == SubGhzCustomEventManagerSet) {
             return false;
         } else {
             scene_manager_search_and_switch_to_previous_scene(
@@ -61,14 +62,12 @@ bool subghz_scene_show_error_on_event(void* context, SceneManagerEvent event) {
         return true;
     } else if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SubGhzCustomEventSceneShowErrorOk) {
-            if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneShowError) ==
-               SubGhzCustomEventManagerSet) {
+            if(scene_state == SubGhzCustomEventManagerSet) {
                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneStart);
             }
             return true;
         } else if(event.event == SubGhzCustomEventSceneShowErrorBack) {
-            if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneShowError) ==
-               SubGhzCustomEventManagerSet) {
+            if(scene_state == SubGhzCustomEventManagerSet) {
                 //exit app
                 if(!scene_manager_previous_scene(subghz->scene_manager)) {
                     scene_manager_stop(subghz->scene_manager);

+ 1 - 1
applications/main/subghz/scenes/subghz_scene_start.c

@@ -70,7 +70,7 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) {
         if(event.event == SubmenuIndexReadRAW) {
             scene_manager_set_scene_state(
                 subghz->scene_manager, SubGhzSceneStart, SubmenuIndexReadRAW);
-            subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
+            subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE);
             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReadRAW);
             return true;
         } else if(event.event == SubmenuIndexRead) {

+ 15 - 31
applications/main/subghz/scenes/subghz_scene_transmitter.c

@@ -11,32 +11,24 @@ void subghz_scene_transmitter_callback(SubGhzCustomEvent event, void* context) {
 bool subghz_scene_transmitter_update_data_show(void* context) {
     SubGhz* subghz = context;
     bool ret = false;
-    if(subghz->txrx->decoder_result) {
-        FuriString* key_str;
-        FuriString* frequency_str;
-        FuriString* modulation_str;
+    SubGhzProtocolDecoderBase* decoder = subghz_txrx_get_decoder(subghz->txrx);
 
-        key_str = furi_string_alloc();
-        frequency_str = furi_string_alloc();
-        modulation_str = furi_string_alloc();
-        uint8_t show_button = 0;
+    if(decoder) {
+        FuriString* key_str = furi_string_alloc();
+        FuriString* frequency_str = furi_string_alloc();
+        FuriString* modulation_str = furi_string_alloc();
 
         if(subghz_protocol_decoder_base_deserialize(
-               subghz->txrx->decoder_result, subghz->txrx->fff_data) == SubGhzProtocolStatusOk) {
-            subghz_protocol_decoder_base_get_string(subghz->txrx->decoder_result, key_str);
+               decoder, subghz_txrx_get_fff_data(subghz->txrx)) == SubGhzProtocolStatusOk) {
+            subghz_protocol_decoder_base_get_string(decoder, key_str);
 
-            if((subghz->txrx->decoder_result->protocol->flag & SubGhzProtocolFlag_Send) ==
-               SubGhzProtocolFlag_Send) {
-                show_button = 1;
-            }
-
-            subghz_get_frequency_modulation(subghz, frequency_str, modulation_str);
+            subghz_txrx_get_frequency_and_modulation(subghz->txrx, frequency_str, modulation_str);
             subghz_view_transmitter_add_data_to_show(
                 subghz->subghz_transmitter,
                 furi_string_get_cstr(key_str),
                 furi_string_get_cstr(frequency_str),
                 furi_string_get_cstr(modulation_str),
-                show_button);
+                subghz_txrx_protocol_is_transmittable(subghz->txrx, false));
             ret = true;
         }
         furi_string_free(frequency_str);
@@ -65,24 +57,16 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) {
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SubGhzCustomEventViewTransmitterSendStart) {
             subghz->state_notifications = SubGhzNotificationStateIDLE;
-            if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
-                subghz_rx_end(subghz);
-            }
-            if((subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) ||
-               (subghz->txrx->txrx_state == SubGhzTxRxStateSleep)) {
-                if(subghz_tx_start(subghz, subghz->txrx->fff_data)) {
-                    subghz->state_notifications = SubGhzNotificationStateTx;
-                    subghz_scene_transmitter_update_data_show(subghz);
-                    DOLPHIN_DEED(DolphinDeedSubGhzSend);
-                }
+
+            if(subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx))) {
+                subghz->state_notifications = SubGhzNotificationStateTx;
+                subghz_scene_transmitter_update_data_show(subghz);
+                DOLPHIN_DEED(DolphinDeedSubGhzSend);
             }
             return true;
         } else if(event.event == SubGhzCustomEventViewTransmitterSendStop) {
             subghz->state_notifications = SubGhzNotificationStateIDLE;
-            if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) {
-                subghz_tx_stop(subghz);
-                subghz_sleep(subghz);
-            }
+            subghz_txrx_stop(subghz->txrx);
             return true;
         } else if(event.event == SubGhzCustomEventViewTransmitterBack) {
             subghz->state_notifications = SubGhzNotificationStateIDLE;

+ 24 - 71
applications/main/subghz/subghz.c

@@ -1,9 +1,6 @@
 /* Abandon hope, all ye who enter here. */
 
-#include "subghz/types.h"
 #include "subghz_i.h"
-#include <lib/toolbox/path.h>
-#include <lib/subghz/protocols/protocol_items.h>
 
 bool subghz_custom_event_callback(void* context, uint32_t event) {
     furi_assert(context);
@@ -49,16 +46,6 @@ static void subghz_rpc_command_callback(RpcAppSystemEvent event, void* context)
     }
 }
 
-void subghz_blink_start(SubGhz* instance) {
-    furi_assert(instance);
-    notification_message(instance->notifications, &sequence_blink_start_magenta);
-}
-
-void subghz_blink_stop(SubGhz* instance) {
-    furi_assert(instance);
-    notification_message(instance->notifications, &sequence_blink_stop);
-}
-
 SubGhz* subghz_alloc() {
     SubGhz* subghz = malloc(sizeof(SubGhz));
 
@@ -163,45 +150,18 @@ SubGhz* subghz_alloc() {
         SubGhzViewIdStatic,
         subghz_test_static_get_view(subghz->subghz_test_static));
 
-    //init setting
-    subghz->setting = subghz_setting_alloc();
-    subghz_setting_load(subghz->setting, EXT_PATH("subghz/assets/setting_user"));
-
-    //init Worker & Protocol & History & KeyBoard
-    subghz->lock = SubGhzLockOff;
-    subghz->txrx = malloc(sizeof(SubGhzTxRx));
-    subghz->txrx->preset = malloc(sizeof(SubGhzRadioPreset));
-    subghz->txrx->preset->name = furi_string_alloc();
-    subghz_preset_init(
-        subghz, "AM650", subghz_setting_get_default_frequency(subghz->setting), NULL, 0);
-
-    subghz->txrx->txrx_state = SubGhzTxRxStateSleep;
-    subghz->txrx->hopper_state = SubGhzHopperStateOFF;
-    subghz->txrx->speaker_state = SubGhzSpeakerStateDisable;
-    subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
-    subghz->txrx->raw_threshold_rssi = SUBGHZ_RAW_TRESHOLD_MIN;
-    subghz->txrx->history = subghz_history_alloc();
-    subghz->txrx->worker = subghz_worker_alloc();
-    subghz->txrx->fff_data = flipper_format_string_alloc();
-
-    subghz->txrx->environment = subghz_environment_alloc();
-    subghz_environment_set_came_atomo_rainbow_table_file_name(
-        subghz->txrx->environment, EXT_PATH("subghz/assets/came_atomo"));
-    subghz_environment_set_alutech_at_4n_rainbow_table_file_name(
-        subghz->txrx->environment, EXT_PATH("subghz/assets/alutech_at_4n"));
-    subghz_environment_set_nice_flor_s_rainbow_table_file_name(
-        subghz->txrx->environment, EXT_PATH("subghz/assets/nice_flor_s"));
-    subghz_environment_set_protocol_registry(
-        subghz->txrx->environment, (void*)&subghz_protocol_registry);
-    subghz->txrx->receiver = subghz_receiver_alloc_init(subghz->txrx->environment);
-    subghz->txrx->filter = SubGhzProtocolFlag_Decodable;
-    subghz_receiver_set_filter(subghz->txrx->receiver, subghz->txrx->filter);
-
-    subghz_worker_set_overrun_callback(
-        subghz->txrx->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset);
-    subghz_worker_set_pair_callback(
-        subghz->txrx->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode);
-    subghz_worker_set_context(subghz->txrx->worker, subghz->txrx->receiver);
+    //init threshold rssi
+    subghz->threshold_rssi = subghz_threshold_rssi_alloc();
+
+    subghz_unlock(subghz);
+    subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE);
+    subghz->history = subghz_history_alloc();
+    subghz->filter = SubGhzProtocolFlag_Decodable;
+
+    //init TxRx & History & KeyBoard
+    subghz->txrx = subghz_txrx_alloc();
+    subghz_txrx_receiver_set_filter(subghz->txrx, subghz->filter);
+    subghz_txrx_set_need_save_callback(subghz->txrx, subghz_save_to_file, subghz);
 
     //Init Error_str
     subghz->error_str = furi_string_alloc();
@@ -219,7 +179,9 @@ void subghz_free(SubGhz* subghz) {
         subghz->rpc_ctx = NULL;
     }
 
-    subghz_speaker_off(subghz);
+    subghz_txrx_speaker_off(subghz->txrx);
+    subghz_txrx_stop(subghz->txrx);
+    subghz_txrx_sleep(subghz->txrx);
 
     // Packet Test
     view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdTestPacket);
@@ -282,18 +244,14 @@ void subghz_free(SubGhz* subghz) {
     furi_record_close(RECORD_GUI);
     subghz->gui = NULL;
 
-    //setting
-    subghz_setting_free(subghz->setting);
+    // threshold rssi
+    subghz_threshold_rssi_free(subghz->threshold_rssi);
 
     //Worker & Protocol & History
-    subghz_receiver_free(subghz->txrx->receiver);
-    subghz_environment_free(subghz->txrx->environment);
-    subghz_worker_free(subghz->txrx->worker);
-    flipper_format_free(subghz->txrx->fff_data);
-    subghz_history_free(subghz->txrx->history);
-    furi_string_free(subghz->txrx->preset->name);
-    free(subghz->txrx->preset);
-    free(subghz->txrx);
+    subghz_history_free(subghz->history);
+
+    //TxRx
+    subghz_txrx_free(subghz->txrx);
 
     //Error string
     furi_string_free(subghz->error_str);
@@ -319,11 +277,6 @@ int32_t subghz_app(void* p) {
         return 1;
     }
 
-    //Load database
-    bool load_database = subghz_environment_load_keystore(
-        subghz->txrx->environment, EXT_PATH("subghz/assets/keeloq_mfcodes"));
-    subghz_environment_load_keystore(
-        subghz->txrx->environment, EXT_PATH("subghz/assets/keeloq_mfcodes_user"));
     // Check argument and run corresponding scene
     if(p && strlen(p)) {
         uint32_t rpc_ctx = 0;
@@ -340,9 +293,9 @@ int32_t subghz_app(void* p) {
             if(subghz_key_load(subghz, p, true)) {
                 furi_string_set(subghz->file_path, (const char*)p);
 
-                if((!strcmp(subghz->txrx->decoder_result->protocol->name, "RAW"))) {
+                if(subghz_get_load_type_file(subghz) == SubGhzLoadTypeFileRaw) {
                     //Load Raw TX
-                    subghz->txrx->rx_key_state = SubGhzRxKeyStateRAWLoad;
+                    subghz_rx_key_state_set(subghz, SubGhzRxKeyStateRAWLoad);
                     scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReadRAW);
                 } else {
                     //Load transmitter TX
@@ -358,7 +311,7 @@ int32_t subghz_app(void* p) {
         view_dispatcher_attach_to_gui(
             subghz->view_dispatcher, subghz->gui, ViewDispatcherTypeFullscreen);
         furi_string_set(subghz->file_path, SUBGHZ_APP_FOLDER);
-        if(load_database) {
+        if(subghz_txrx_is_database_loaded(subghz->txrx)) {
             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneStart);
         } else {
             scene_manager_set_scene_state(

+ 90 - 313
applications/main/subghz/subghz_i.c

@@ -18,214 +18,42 @@
 
 #define TAG "SubGhz"
 
-void subghz_preset_init(
-    void* context,
-    const char* preset_name,
-    uint32_t frequency,
-    uint8_t* preset_data,
-    size_t preset_data_size) {
-    furi_assert(context);
-    SubGhz* subghz = context;
-    furi_string_set(subghz->txrx->preset->name, preset_name);
-    subghz->txrx->preset->frequency = frequency;
-    subghz->txrx->preset->data = preset_data;
-    subghz->txrx->preset->data_size = preset_data_size;
-}
-
-bool subghz_set_preset(SubGhz* subghz, const char* preset) {
-    if(!strcmp(preset, "FuriHalSubGhzPresetOok270Async")) {
-        furi_string_set(subghz->txrx->preset->name, "AM270");
-    } else if(!strcmp(preset, "FuriHalSubGhzPresetOok650Async")) {
-        furi_string_set(subghz->txrx->preset->name, "AM650");
-    } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev238Async")) {
-        furi_string_set(subghz->txrx->preset->name, "FM238");
-    } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev476Async")) {
-        furi_string_set(subghz->txrx->preset->name, "FM476");
-    } else if(!strcmp(preset, "FuriHalSubGhzPresetCustom")) {
-        furi_string_set(subghz->txrx->preset->name, "CUSTOM");
-    } else {
-        FURI_LOG_E(TAG, "Unknown preset");
-        return false;
-    }
-    return true;
-}
-
-void subghz_get_frequency_modulation(SubGhz* subghz, FuriString* frequency, FuriString* modulation) {
+void subghz_set_default_preset(SubGhz* subghz) {
     furi_assert(subghz);
-    if(frequency != NULL) {
-        furi_string_printf(
-            frequency,
-            "%03ld.%02ld",
-            subghz->txrx->preset->frequency / 1000000 % 1000,
-            subghz->txrx->preset->frequency / 10000 % 100);
-    }
-    if(modulation != NULL) {
-        furi_string_printf(modulation, "%.2s", furi_string_get_cstr(subghz->txrx->preset->name));
-    }
+    subghz_txrx_set_preset(
+        subghz->txrx,
+        "AM650",
+        subghz_setting_get_default_frequency(subghz_txrx_get_setting(subghz->txrx)),
+        NULL,
+        0);
 }
 
-void subghz_begin(SubGhz* subghz, uint8_t* preset_data) {
+void subghz_blink_start(SubGhz* subghz) {
     furi_assert(subghz);
-    furi_hal_subghz_reset();
-    furi_hal_subghz_idle();
-    furi_hal_subghz_load_custom_preset(preset_data);
-    furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow);
-    subghz->txrx->txrx_state = SubGhzTxRxStateIDLE;
+    notification_message(subghz->notifications, &sequence_blink_stop);
+    notification_message(subghz->notifications, &sequence_blink_start_magenta);
 }
 
-uint32_t subghz_rx(SubGhz* subghz, uint32_t frequency) {
+void subghz_blink_stop(SubGhz* subghz) {
     furi_assert(subghz);
-    if(!furi_hal_subghz_is_frequency_valid(frequency)) {
-        furi_crash("SubGhz: Incorrect RX frequency.");
-    }
-    furi_assert(
-        subghz->txrx->txrx_state != SubGhzTxRxStateRx &&
-        subghz->txrx->txrx_state != SubGhzTxRxStateSleep);
-
-    furi_hal_subghz_idle();
-    uint32_t value = furi_hal_subghz_set_frequency_and_path(frequency);
-    furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow);
-    furi_hal_subghz_flush_rx();
-    subghz_speaker_on(subghz);
-    furi_hal_subghz_rx();
-
-    furi_hal_subghz_start_async_rx(subghz_worker_rx_callback, subghz->txrx->worker);
-    subghz_worker_start(subghz->txrx->worker);
-    subghz->txrx->txrx_state = SubGhzTxRxStateRx;
-    return value;
-}
-
-static bool subghz_tx(SubGhz* subghz, uint32_t frequency) {
-    furi_assert(subghz);
-    if(!furi_hal_subghz_is_frequency_valid(frequency)) {
-        furi_crash("SubGhz: Incorrect TX frequency.");
-    }
-    furi_assert(subghz->txrx->txrx_state != SubGhzTxRxStateSleep);
-    furi_hal_subghz_idle();
-    furi_hal_subghz_set_frequency_and_path(frequency);
-    furi_hal_gpio_write(&gpio_cc1101_g0, false);
-    furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
-    bool ret = furi_hal_subghz_tx();
-    if(ret) {
-        subghz_speaker_on(subghz);
-        subghz->txrx->txrx_state = SubGhzTxRxStateTx;
-    }
-    return ret;
-}
-
-void subghz_idle(SubGhz* subghz) {
-    furi_assert(subghz);
-    furi_assert(subghz->txrx->txrx_state != SubGhzTxRxStateSleep);
-    furi_hal_subghz_idle();
-    subghz_speaker_off(subghz);
-    subghz->txrx->txrx_state = SubGhzTxRxStateIDLE;
-}
-
-void subghz_rx_end(SubGhz* subghz) {
-    furi_assert(subghz);
-    furi_assert(subghz->txrx->txrx_state == SubGhzTxRxStateRx);
-
-    if(subghz_worker_is_running(subghz->txrx->worker)) {
-        subghz_worker_stop(subghz->txrx->worker);
-        furi_hal_subghz_stop_async_rx();
-    }
-    furi_hal_subghz_idle();
-    subghz_speaker_off(subghz);
-    subghz->txrx->txrx_state = SubGhzTxRxStateIDLE;
-}
-
-void subghz_sleep(SubGhz* subghz) {
-    furi_assert(subghz);
-    furi_hal_subghz_sleep();
-    subghz->txrx->txrx_state = SubGhzTxRxStateSleep;
+    notification_message(subghz->notifications, &sequence_blink_stop);
 }
 
 bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format) {
-    furi_assert(subghz);
-
-    bool ret = false;
-    FuriString* temp_str;
-    temp_str = furi_string_alloc();
-    uint32_t repeat = 200;
-    do {
-        if(!flipper_format_rewind(flipper_format)) {
-            FURI_LOG_E(TAG, "Rewind error");
-            break;
-        }
-        if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) {
-            FURI_LOG_E(TAG, "Missing Protocol");
-            break;
-        }
-        if(!flipper_format_insert_or_update_uint32(flipper_format, "Repeat", &repeat, 1)) {
-            FURI_LOG_E(TAG, "Unable Repeat");
-            break;
-        }
-
-        subghz->txrx->transmitter = subghz_transmitter_alloc_init(
-            subghz->txrx->environment, furi_string_get_cstr(temp_str));
-
-        if(subghz->txrx->transmitter) {
-            if(subghz_transmitter_deserialize(subghz->txrx->transmitter, flipper_format) ==
-               SubGhzProtocolStatusOk) {
-                if(strcmp(furi_string_get_cstr(subghz->txrx->preset->name), "") != 0) {
-                    subghz_begin(
-                        subghz,
-                        subghz_setting_get_preset_data_by_name(
-                            subghz->setting, furi_string_get_cstr(subghz->txrx->preset->name)));
-                } else {
-                    FURI_LOG_E(
-                        TAG,
-                        "Unknown name preset \" %s \"",
-                        furi_string_get_cstr(subghz->txrx->preset->name));
-                    subghz_begin(
-                        subghz, subghz_setting_get_preset_data_by_name(subghz->setting, "AM650"));
-                }
-                if(subghz->txrx->preset->frequency) {
-                    ret = subghz_tx(subghz, subghz->txrx->preset->frequency);
-                } else {
-                    ret = subghz_tx(subghz, 433920000);
-                }
-                if(ret) {
-                    //Start TX
-                    furi_hal_subghz_start_async_tx(
-                        subghz_transmitter_yield, subghz->txrx->transmitter);
-                } else {
-                    subghz_dialog_message_show_only_rx(subghz);
-                }
-            } else {
-                dialog_message_show_storage_error(
-                    subghz->dialogs, "Error in protocol\nparameters\ndescription");
-            }
-        }
-        if(!ret) {
-            subghz_transmitter_free(subghz->txrx->transmitter);
-            if(subghz->txrx->txrx_state != SubGhzTxRxStateSleep) {
-                subghz_idle(subghz);
-            }
-        }
-
-    } while(false);
-    furi_string_free(temp_str);
-    return ret;
-}
+    switch(subghz_txrx_tx_start(subghz->txrx, flipper_format)) {
+    case SubGhzTxRxStartTxStateErrorParserOthers:
+        dialog_message_show_storage_error(
+            subghz->dialogs, "Error in protocol\nparameters\ndescription");
+        break;
+    case SubGhzTxRxStartTxStateErrorOnlyRx:
+        subghz_dialog_message_show_only_rx(subghz);
+        break;
 
-void subghz_tx_stop(SubGhz* subghz) {
-    furi_assert(subghz);
-    furi_assert(subghz->txrx->txrx_state == SubGhzTxRxStateTx);
-    //Stop TX
-    furi_hal_subghz_stop_async_tx();
-    subghz_transmitter_stop(subghz->txrx->transmitter);
-    subghz_transmitter_free(subghz->txrx->transmitter);
-
-    //if protocol dynamic then we save the last upload
-    if((subghz->txrx->decoder_result->protocol->type == SubGhzProtocolTypeDynamic) &&
-       (subghz_path_is_file(subghz->file_path))) {
-        subghz_save_protocol_to_file(
-            subghz, subghz->txrx->fff_data, furi_string_get_cstr(subghz->file_path));
+    default:
+        return true;
+        break;
     }
-    subghz_idle(subghz);
-    subghz_speaker_off(subghz);
-    notification_message(subghz->notifications, &sequence_reset_red);
+    return false;
 }
 
 void subghz_dialog_message_show_only_rx(SubGhz* subghz) {
@@ -254,11 +82,11 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) {
 
     Storage* storage = furi_record_open(RECORD_STORAGE);
     FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
-    Stream* fff_data_stream = flipper_format_get_raw_stream(subghz->txrx->fff_data);
+    Stream* fff_data_stream =
+        flipper_format_get_raw_stream(subghz_txrx_get_fff_data(subghz->txrx));
 
     SubGhzLoadKeyState load_key_state = SubGhzLoadKeyStateParseErr;
-    FuriString* temp_str;
-    temp_str = furi_string_alloc();
+    FuriString* temp_str = furi_string_alloc();
     uint32_t temp_data32;
 
     do {
@@ -281,6 +109,7 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) {
             break;
         }
 
+        //Load frequency
         if(!flipper_format_read_uint32(fff_data_file, "Frequency", &temp_data32, 1)) {
             FURI_LOG_E(TAG, "Missing Frequency");
             break;
@@ -291,58 +120,61 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) {
             break;
         }
 
-        subghz->txrx->preset->frequency = temp_data32;
-
+        //Load preset
         if(!flipper_format_read_string(fff_data_file, "Preset", temp_str)) {
             FURI_LOG_E(TAG, "Missing Preset");
             break;
         }
 
-        if(!subghz_set_preset(subghz, furi_string_get_cstr(temp_str))) {
+        furi_string_set_str(
+            temp_str, subghz_txrx_get_preset_name(subghz->txrx, furi_string_get_cstr(temp_str)));
+        if(!strcmp(furi_string_get_cstr(temp_str), "")) {
             break;
         }
+        SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx);
 
-        if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) {
+        if(!strcmp(furi_string_get_cstr(temp_str), "CUSTOM")) {
             //Todo add Custom_preset_module
             //delete preset if it already exists
-            subghz_setting_delete_custom_preset(
-                subghz->setting, furi_string_get_cstr(subghz->txrx->preset->name));
+            subghz_setting_delete_custom_preset(setting, furi_string_get_cstr(temp_str));
             //load custom preset from file
             if(!subghz_setting_load_custom_preset(
-                   subghz->setting,
-                   furi_string_get_cstr(subghz->txrx->preset->name),
-                   fff_data_file)) {
+                   setting, furi_string_get_cstr(temp_str), fff_data_file)) {
                 FURI_LOG_E(TAG, "Missing Custom preset");
                 break;
             }
         }
-        size_t preset_index = subghz_setting_get_inx_preset_by_name(
-            subghz->setting, furi_string_get_cstr(subghz->txrx->preset->name));
-        subghz_preset_init(
-            subghz,
-            furi_string_get_cstr(subghz->txrx->preset->name),
-            subghz->txrx->preset->frequency,
-            subghz_setting_get_preset_data(subghz->setting, preset_index),
-            subghz_setting_get_preset_data_size(subghz->setting, preset_index));
-
+        size_t preset_index =
+            subghz_setting_get_inx_preset_by_name(setting, furi_string_get_cstr(temp_str));
+        subghz_txrx_set_preset(
+            subghz->txrx,
+            furi_string_get_cstr(temp_str),
+            temp_data32,
+            subghz_setting_get_preset_data(setting, preset_index),
+            subghz_setting_get_preset_data_size(setting, preset_index));
+
+        //Load protocol
         if(!flipper_format_read_string(fff_data_file, "Protocol", temp_str)) {
             FURI_LOG_E(TAG, "Missing Protocol");
             break;
         }
+
+        FlipperFormat* fff_data = subghz_txrx_get_fff_data(subghz->txrx);
         if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) {
             //if RAW
-            subghz_protocol_raw_gen_fff_data(subghz->txrx->fff_data, file_path);
+            subghz->load_type_file = SubGhzLoadTypeFileRaw;
+            subghz_protocol_raw_gen_fff_data(fff_data, file_path);
         } else {
+            subghz->load_type_file = SubGhzLoadTypeFileKey;
             stream_copy_full(
                 flipper_format_get_raw_stream(fff_data_file),
-                flipper_format_get_raw_stream(subghz->txrx->fff_data));
+                flipper_format_get_raw_stream(fff_data));
         }
 
-        subghz->txrx->decoder_result = subghz_receiver_search_decoder_base_by_name(
-            subghz->txrx->receiver, furi_string_get_cstr(temp_str));
-        if(subghz->txrx->decoder_result) {
+        if(subghz_txrx_load_decoder_by_name_protocol(
+               subghz->txrx, furi_string_get_cstr(temp_str))) {
             SubGhzProtocolStatus status = subghz_protocol_decoder_base_deserialize(
-                subghz->txrx->decoder_result, subghz->txrx->fff_data);
+                subghz_txrx_get_decoder(subghz->txrx), fff_data);
             if(status != SubGhzProtocolStatusOk) {
                 load_key_state = SubGhzLoadKeyStateProtocolDescriptionErr;
                 break;
@@ -381,17 +213,18 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) {
     }
 }
 
+SubGhzLoadTypeFile subghz_get_load_type_file(SubGhz* subghz) {
+    furi_assert(subghz);
+    return subghz->load_type_file;
+}
+
 bool subghz_get_next_name_file(SubGhz* subghz, uint8_t max_len) {
     furi_assert(subghz);
 
     Storage* storage = furi_record_open(RECORD_STORAGE);
-    FuriString* temp_str;
-    FuriString* file_name;
-    FuriString* file_path;
-
-    temp_str = furi_string_alloc();
-    file_name = furi_string_alloc();
-    file_path = furi_string_alloc();
+    FuriString* temp_str = furi_string_alloc();
+    FuriString* file_name = furi_string_alloc();
+    FuriString* file_path = furi_string_alloc();
 
     bool res = false;
 
@@ -438,8 +271,7 @@ bool subghz_save_protocol_to_file(
     Stream* flipper_format_stream = flipper_format_get_raw_stream(flipper_format);
 
     bool saved = false;
-    FuriString* file_dir;
-    file_dir = furi_string_alloc();
+    FuriString* file_dir = furi_string_alloc();
 
     path_extract_dirname(dev_file_name, file_dir);
     do {
@@ -467,11 +299,21 @@ bool subghz_save_protocol_to_file(
     return saved;
 }
 
+void subghz_save_to_file(void* context) {
+    furi_assert(context);
+    SubGhz* subghz = context;
+    if(subghz_path_is_file(subghz->file_path)) {
+        subghz_save_protocol_to_file(
+            subghz,
+            subghz_txrx_get_fff_data(subghz->txrx),
+            furi_string_get_cstr(subghz->file_path));
+    }
+}
+
 bool subghz_load_protocol_from_file(SubGhz* subghz) {
     furi_assert(subghz);
 
-    FuriString* file_path;
-    file_path = furi_string_alloc();
+    FuriString* file_path = furi_string_alloc();
 
     DialogsFileBrowserOptions browser_options;
     dialog_file_browser_set_basic_options(&browser_options, SUBGHZ_APP_EXTENSION, &I_sub1_10px);
@@ -551,92 +393,27 @@ bool subghz_path_is_file(FuriString* path) {
     return furi_string_end_with(path, SUBGHZ_APP_EXTENSION);
 }
 
-uint32_t subghz_random_serial(void) {
-    return (uint32_t)rand();
-}
-
-void subghz_hopper_update(SubGhz* subghz) {
+void subghz_lock(SubGhz* subghz) {
     furi_assert(subghz);
-
-    switch(subghz->txrx->hopper_state) {
-    case SubGhzHopperStateOFF:
-    case SubGhzHopperStatePause:
-        return;
-    case SubGhzHopperStateRSSITimeOut:
-        if(subghz->txrx->hopper_timeout != 0) {
-            subghz->txrx->hopper_timeout--;
-            return;
-        }
-        break;
-    default:
-        break;
-    }
-    float rssi = -127.0f;
-    if(subghz->txrx->hopper_state != SubGhzHopperStateRSSITimeOut) {
-        // See RSSI Calculation timings in CC1101 17.3 RSSI
-        rssi = furi_hal_subghz_get_rssi();
-
-        // Stay if RSSI is high enough
-        if(rssi > -90.0f) {
-            subghz->txrx->hopper_timeout = 10;
-            subghz->txrx->hopper_state = SubGhzHopperStateRSSITimeOut;
-            return;
-        }
-    } else {
-        subghz->txrx->hopper_state = SubGhzHopperStateRunnig;
-    }
-    // Select next frequency
-    if(subghz->txrx->hopper_idx_frequency <
-       subghz_setting_get_hopper_frequency_count(subghz->setting) - 1) {
-        subghz->txrx->hopper_idx_frequency++;
-    } else {
-        subghz->txrx->hopper_idx_frequency = 0;
-    }
-
-    if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
-        subghz_rx_end(subghz);
-    };
-    if(subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) {
-        subghz_receiver_reset(subghz->txrx->receiver);
-        subghz->txrx->preset->frequency = subghz_setting_get_hopper_frequency(
-            subghz->setting, subghz->txrx->hopper_idx_frequency);
-        subghz_rx(subghz, subghz->txrx->preset->frequency);
-    }
+    subghz->lock = SubGhzLockOn;
 }
 
-void subghz_speaker_on(SubGhz* subghz) {
-    if(subghz->txrx->speaker_state == SubGhzSpeakerStateEnable) {
-        if(furi_hal_speaker_acquire(30)) {
-            furi_hal_subghz_set_async_mirror_pin(&gpio_speaker);
-        } else {
-            subghz->txrx->speaker_state = SubGhzSpeakerStateDisable;
-        }
-    }
+void subghz_unlock(SubGhz* subghz) {
+    furi_assert(subghz);
+    subghz->lock = SubGhzLockOff;
 }
 
-void subghz_speaker_off(SubGhz* subghz) {
-    if(subghz->txrx->speaker_state != SubGhzSpeakerStateDisable) {
-        if(furi_hal_speaker_is_mine()) {
-            furi_hal_subghz_set_async_mirror_pin(NULL);
-            furi_hal_speaker_release();
-            if(subghz->txrx->speaker_state == SubGhzSpeakerStateShutdown)
-                subghz->txrx->speaker_state = SubGhzSpeakerStateDisable;
-        }
-    }
+bool subghz_is_locked(SubGhz* subghz) {
+    furi_assert(subghz);
+    return (subghz->lock == SubGhzLockOn);
 }
 
-void subghz_speaker_mute(SubGhz* subghz) {
-    if(subghz->txrx->speaker_state == SubGhzSpeakerStateEnable) {
-        if(furi_hal_speaker_is_mine()) {
-            furi_hal_subghz_set_async_mirror_pin(NULL);
-        }
-    }
+void subghz_rx_key_state_set(SubGhz* subghz, SubGhzRxKeyState state) {
+    furi_assert(subghz);
+    subghz->rx_key_state = state;
 }
 
-void subghz_speaker_unmute(SubGhz* subghz) {
-    if(subghz->txrx->speaker_state == SubGhzSpeakerStateEnable) {
-        if(furi_hal_speaker_is_mine()) {
-            furi_hal_subghz_set_async_mirror_pin(&gpio_speaker);
-        }
-    }
+SubGhzRxKeyState subghz_rx_key_state_get(SubGhz* subghz) {
+    furi_assert(subghz);
+    return subghz->rx_key_state;
 }

+ 23 - 53
applications/main/subghz/subghz_i.h

@@ -25,10 +25,6 @@
 #include <gui/modules/widget.h>
 
 #include <subghz/scenes/subghz_scene.h>
-#include <lib/subghz/subghz_worker.h>
-#include <lib/subghz/subghz_setting.h>
-#include <lib/subghz/receiver.h>
-#include <lib/subghz/transmitter.h>
 
 #include "subghz_history.h"
 
@@ -37,33 +33,11 @@
 
 #include "rpc/rpc_app.h"
 
-#define SUBGHZ_MAX_LEN_NAME 64
-
-struct SubGhzTxRx {
-    SubGhzWorker* worker;
-
-    SubGhzEnvironment* environment;
-    SubGhzReceiver* receiver;
-    SubGhzTransmitter* transmitter;
-    SubGhzProtocolFlag filter;
-    SubGhzProtocolDecoderBase* decoder_result;
-    FlipperFormat* fff_data;
-
-    SubGhzRadioPreset* preset;
-    SubGhzHistory* history;
-    uint16_t idx_menu_chosen;
-    SubGhzTxRxState txrx_state;
-    SubGhzHopperState hopper_state;
-    SubGhzSpeakerState speaker_state;
-    uint8_t hopper_timeout;
-    uint8_t hopper_idx_frequency;
-    SubGhzRxKeyState rx_key_state;
+#include "helpers/subghz_threshold_rssi.h"
 
-    float raw_threshold_rssi;
-    uint8_t raw_threshold_rssi_low_count;
-};
+#include "helpers/subghz_txrx.h"
 
-typedef struct SubGhzTxRx SubGhzTxRx;
+#define SUBGHZ_MAX_LEN_NAME 64
 
 struct SubGhz {
     Gui* gui;
@@ -93,47 +67,43 @@ struct SubGhz {
     SubGhzTestStatic* subghz_test_static;
     SubGhzTestCarrier* subghz_test_carrier;
     SubGhzTestPacket* subghz_test_packet;
+
+    SubGhzProtocolFlag filter;
     FuriString* error_str;
-    SubGhzSetting* setting;
     SubGhzLock lock;
-
+    SubGhzThresholdRssi* threshold_rssi;
+    SubGhzRxKeyState rx_key_state;
+    SubGhzHistory* history;
+    uint16_t idx_menu_chosen;
+    SubGhzLoadTypeFile load_type_file;
     void* rpc_ctx;
 };
 
-void subghz_preset_init(
-    void* context,
-    const char* preset_name,
-    uint32_t frequency,
-    uint8_t* preset_data,
-    size_t preset_data_size);
-bool subghz_set_preset(SubGhz* subghz, const char* preset);
-void subghz_get_frequency_modulation(SubGhz* subghz, FuriString* frequency, FuriString* modulation);
-void subghz_begin(SubGhz* subghz, uint8_t* preset_data);
-uint32_t subghz_rx(SubGhz* subghz, uint32_t frequency);
-void subghz_rx_end(SubGhz* subghz);
-void subghz_sleep(SubGhz* subghz);
-
-void subghz_blink_start(SubGhz* instance);
-void subghz_blink_stop(SubGhz* instance);
+void subghz_set_default_preset(SubGhz* subghz);
+void subghz_blink_start(SubGhz* subghz);
+void subghz_blink_stop(SubGhz* subghz);
 
 bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format);
-void subghz_tx_stop(SubGhz* subghz);
 void subghz_dialog_message_show_only_rx(SubGhz* subghz);
+
 bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog);
 bool subghz_get_next_name_file(SubGhz* subghz, uint8_t max_len);
 bool subghz_save_protocol_to_file(
     SubGhz* subghz,
     FlipperFormat* flipper_format,
     const char* dev_file_name);
+void subghz_save_to_file(void* context);
 bool subghz_load_protocol_from_file(SubGhz* subghz);
 bool subghz_rename_file(SubGhz* subghz);
 bool subghz_file_available(SubGhz* subghz);
 bool subghz_delete_file(SubGhz* subghz);
 void subghz_file_name_clear(SubGhz* subghz);
 bool subghz_path_is_file(FuriString* path);
-uint32_t subghz_random_serial(void);
-void subghz_hopper_update(SubGhz* subghz);
-void subghz_speaker_on(SubGhz* subghz);
-void subghz_speaker_off(SubGhz* subghz);
-void subghz_speaker_mute(SubGhz* subghz);
-void subghz_speaker_unmute(SubGhz* subghz);
+SubGhzLoadTypeFile subghz_get_load_type_file(SubGhz* subghz);
+
+void subghz_lock(SubGhz* subghz);
+void subghz_unlock(SubGhz* subghz);
+bool subghz_is_locked(SubGhz* subghz);
+
+void subghz_rx_key_state_set(SubGhz* subghz, SubGhzRxKeyState state);
+SubGhzRxKeyState subghz_rx_key_state_get(SubGhz* subghz);

+ 12 - 11
applications/main/subghz/views/receiver.c

@@ -12,7 +12,7 @@
 #define MENU_ITEMS 4u
 #define UNLOCK_CNT 3
 
-#define SUBGHZ_RAW_TRESHOLD_MIN -90.0f
+#define SUBGHZ_RAW_THRESHOLD_MIN -90.0f
 
 typedef struct {
     FuriString* item_str;
@@ -44,7 +44,7 @@ typedef enum {
 } SubGhzViewReceiverBarShow;
 
 struct SubGhzViewReceiver {
-    SubGhzLock lock;
+    bool lock;
     uint8_t lock_count;
     FuriTimer* timer;
     View* view;
@@ -70,20 +70,21 @@ void subghz_receiver_rssi(SubGhzViewReceiver* instance, float rssi) {
         instance->view,
         SubGhzViewReceiverModel * model,
         {
-            if(rssi < SUBGHZ_RAW_TRESHOLD_MIN) {
+            if(rssi < SUBGHZ_RAW_THRESHOLD_MIN) {
                 model->u_rssi = 0;
             } else {
-                model->u_rssi = (uint8_t)(rssi - SUBGHZ_RAW_TRESHOLD_MIN);
+                model->u_rssi = (uint8_t)(rssi - SUBGHZ_RAW_THRESHOLD_MIN);
             }
         },
         true);
 }
 
-void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, SubGhzLock lock) {
+void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, bool lock) {
     furi_assert(subghz_receiver);
     subghz_receiver->lock_count = 0;
-    if(lock == SubGhzLockOn) {
-        subghz_receiver->lock = lock;
+
+    if(lock == true) {
+        subghz_receiver->lock = true;
         with_view_model(
             subghz_receiver->view,
             SubGhzViewReceiverModel * model,
@@ -280,7 +281,7 @@ static void subghz_view_receiver_timer_callback(void* context) {
         subghz_receiver->callback(
             SubGhzCustomEventViewReceiverOffDisplay, subghz_receiver->context);
     } else {
-        subghz_receiver->lock = SubGhzLockOff;
+        subghz_receiver->lock = false;
         subghz_receiver->callback(SubGhzCustomEventViewReceiverUnlock, subghz_receiver->context);
     }
     subghz_receiver->lock_count = 0;
@@ -290,7 +291,7 @@ bool subghz_view_receiver_input(InputEvent* event, void* context) {
     furi_assert(context);
     SubGhzViewReceiver* subghz_receiver = context;
 
-    if(subghz_receiver->lock == SubGhzLockOn) {
+    if(subghz_receiver->lock == true) {
         with_view_model(
             subghz_receiver->view,
             SubGhzViewReceiverModel * model,
@@ -310,7 +311,7 @@ bool subghz_view_receiver_input(InputEvent* event, void* context) {
                 SubGhzViewReceiverModel * model,
                 { model->bar_show = SubGhzViewReceiverBarShowUnlock; },
                 true);
-            //subghz_receiver->lock = SubGhzLockOff;
+            //subghz_receiver->lock = false;
             furi_timer_start(subghz_receiver->timer, pdMS_TO_TICKS(650));
         }
 
@@ -394,7 +395,7 @@ SubGhzViewReceiver* subghz_view_receiver_alloc() {
     // View allocation and configuration
     subghz_receiver->view = view_alloc();
 
-    subghz_receiver->lock = SubGhzLockOff;
+    subghz_receiver->lock = false;
     subghz_receiver->lock_count = 0;
     view_allocate_model(
         subghz_receiver->view, ViewModelTypeLocking, sizeof(SubGhzViewReceiverModel));

+ 1 - 1
applications/main/subghz/views/receiver.h

@@ -10,7 +10,7 @@ typedef void (*SubGhzViewReceiverCallback)(SubGhzCustomEvent event, void* contex
 
 void subghz_receiver_rssi(SubGhzViewReceiver* instance, float rssi);
 
-void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, SubGhzLock keyboard);
+void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, bool keyboard);
 
 void subghz_view_receiver_set_callback(
     SubGhzViewReceiver* subghz_receiver,

+ 4 - 4
applications/main/subghz/views/subghz_read_raw.c

@@ -60,10 +60,10 @@ void subghz_read_raw_add_data_rssi(SubGhzReadRAW* instance, float rssi, bool tra
     furi_assert(instance);
     uint8_t u_rssi = 0;
 
-    if(rssi < SUBGHZ_RAW_TRESHOLD_MIN) {
+    if(rssi < SUBGHZ_RAW_THRESHOLD_MIN) {
         u_rssi = 0;
     } else {
-        u_rssi = (uint8_t)((rssi - SUBGHZ_RAW_TRESHOLD_MIN) / 2.7);
+        u_rssi = (uint8_t)((rssi - SUBGHZ_RAW_THRESHOLD_MIN) / 2.7);
     }
 
     with_view_model(
@@ -261,9 +261,9 @@ void subghz_read_raw_draw_threshold_rssi(Canvas* canvas, SubGhzReadRAWModel* mod
     uint8_t x = 118;
     uint8_t y = 48;
 
-    if(model->raw_threshold_rssi > SUBGHZ_RAW_TRESHOLD_MIN) {
+    if(model->raw_threshold_rssi > SUBGHZ_RAW_THRESHOLD_MIN) {
         uint8_t x = 118;
-        y -= (uint8_t)((model->raw_threshold_rssi - SUBGHZ_RAW_TRESHOLD_MIN) / 2.7);
+        y -= (uint8_t)((model->raw_threshold_rssi - SUBGHZ_RAW_THRESHOLD_MIN) / 2.7);
 
         uint8_t width = 3;
         for(uint8_t i = 0; i < x; i += width * 2) {

+ 1 - 1
applications/main/subghz/views/subghz_read_raw.h

@@ -3,7 +3,7 @@
 #include <gui/view.h>
 #include "../helpers/subghz_custom_event.h"
 
-#define SUBGHZ_RAW_TRESHOLD_MIN -90.0f
+#define SUBGHZ_RAW_THRESHOLD_MIN -90.0f
 
 typedef struct SubGhzReadRAW SubGhzReadRAW;
 

+ 3 - 3
applications/main/subghz/views/transmitter.c

@@ -14,7 +14,7 @@ typedef struct {
     FuriString* frequency_str;
     FuriString* preset_str;
     FuriString* key_str;
-    uint8_t show_button;
+    bool show_button;
 } SubGhzViewTransmitterModel;
 
 void subghz_view_transmitter_set_callback(
@@ -32,7 +32,7 @@ void subghz_view_transmitter_add_data_to_show(
     const char* key_str,
     const char* frequency_str,
     const char* preset_str,
-    uint8_t show_button) {
+    bool show_button) {
     furi_assert(subghz_transmitter);
     with_view_model(
         subghz_transmitter->view,
@@ -104,7 +104,7 @@ bool subghz_view_transmitter_input(InputEvent* event, void* context) {
                 furi_string_reset(model->frequency_str);
                 furi_string_reset(model->preset_str);
                 furi_string_reset(model->key_str);
-                model->show_button = 0;
+                model->show_button = false;
             },
             false);
         return false;

+ 1 - 1
applications/main/subghz/views/transmitter.h

@@ -23,4 +23,4 @@ void subghz_view_transmitter_add_data_to_show(
     const char* key_str,
     const char* frequency_str,
     const char* preset_str,
-    uint8_t show_button);
+    bool show_button);

+ 0 - 2
applications/services/cli/cli_commands.c

@@ -220,11 +220,9 @@ void cli_command_sysctl_debug(Cli* cli, FuriString* args, void* context) {
     UNUSED(context);
     if(!furi_string_cmp(args, "0")) {
         furi_hal_rtc_reset_flag(FuriHalRtcFlagDebug);
-        loader_update_menu();
         printf("Debug disabled.");
     } else if(!furi_string_cmp(args, "1")) {
         furi_hal_rtc_set_flag(FuriHalRtcFlagDebug);
-        loader_update_menu();
         printf("Debug enabled.");
     } else {
         cli_print_usage("sysctl debug", "<1|0>", furi_string_get_cstr(args));

+ 35 - 6
applications/services/desktop/desktop.c

@@ -6,6 +6,8 @@
 #include <notification/notification_messages.h>
 #include <furi.h>
 #include <furi_hal.h>
+#include <cli/cli.h>
+#include <cli/cli_vcp.h>
 
 #include "animations/animation_manager.h"
 #include "desktop/scenes/desktop_scene.h"
@@ -14,7 +16,7 @@
 #include "desktop/views/desktop_view_pin_input.h"
 #include "desktop/views/desktop_view_pin_timeout.h"
 #include "desktop_i.h"
-#include "helpers/pin_lock.h"
+#include "helpers/pin.h"
 #include "helpers/slideshow_filename.h"
 
 #define TAG "Desktop"
@@ -132,6 +134,14 @@ static void desktop_auto_lock_inhibit(Desktop* desktop) {
 }
 
 void desktop_lock(Desktop* desktop) {
+    furi_hal_rtc_set_flag(FuriHalRtcFlagLock);
+
+    if(desktop->settings.pin_code.length) {
+        Cli* cli = furi_record_open(RECORD_CLI);
+        cli_session_close(cli);
+        furi_record_close(RECORD_CLI);
+    }
+
     desktop_auto_lock_inhibit(desktop);
     scene_manager_set_scene_state(
         desktop->scene_manager, DesktopSceneLocked, SCENE_LOCKED_FIRST_ENTER);
@@ -147,6 +157,14 @@ void desktop_unlock(Desktop* desktop) {
     desktop_view_locked_unlock(desktop->locked_view);
     scene_manager_search_and_switch_to_previous_scene(desktop->scene_manager, DesktopSceneMain);
     desktop_auto_lock_arm(desktop);
+    furi_hal_rtc_reset_flag(FuriHalRtcFlagLock);
+    furi_hal_rtc_set_pin_fails(0);
+
+    if(desktop->settings.pin_code.length) {
+        Cli* cli = furi_record_open(RECORD_CLI);
+        cli_session_open(cli, &cli_vcp);
+        furi_record_close(RECORD_CLI);
+    }
 }
 
 void desktop_set_dummy_mode_state(Desktop* desktop, bool enabled) {
@@ -290,11 +308,14 @@ Desktop* desktop_alloc() {
     desktop->auto_lock_timer =
         furi_timer_alloc(desktop_auto_lock_timer_callback, FuriTimerTypeOnce, desktop);
 
+    furi_record_create(RECORD_DESKTOP, desktop);
+
     return desktop;
 }
 
 void desktop_free(Desktop* desktop) {
     furi_assert(desktop);
+    furi_check(furi_record_destroy(RECORD_DESKTOP));
 
     furi_pubsub_unsubscribe(
         loader_get_pubsub(desktop->loader), desktop->app_start_stop_subscription);
@@ -352,6 +373,16 @@ static bool desktop_check_file_flag(const char* flag_path) {
     return exists;
 }
 
+bool desktop_api_is_locked(Desktop* instance) {
+    furi_assert(instance);
+    return furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock);
+}
+
+void desktop_api_unlock(Desktop* instance) {
+    furi_assert(instance);
+    view_dispatcher_send_custom_event(instance->view_dispatcher, DesktopLockedEventUnlocked);
+}
+
 int32_t desktop_srv(void* p) {
     UNUSED(p);
 
@@ -375,14 +406,12 @@ int32_t desktop_srv(void* p) {
 
     scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain);
 
-    desktop_pin_lock_init(&desktop->settings);
-
-    if(!desktop_pin_lock_is_locked()) {
+    if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock)) {
+        desktop_lock(desktop);
+    } else {
         if(!loader_is_locked(desktop->loader)) {
             desktop_auto_lock_arm(desktop);
         }
-    } else {
-        desktop_lock(desktop);
     }
 
     if(desktop_check_file_flag(SLIDESHOW_FS_PATH)) {

+ 6 - 0
applications/services/desktop/desktop.h

@@ -1,3 +1,9 @@
 #pragma once
 
 typedef struct Desktop Desktop;
+
+#define RECORD_DESKTOP "desktop"
+
+bool desktop_api_is_locked(Desktop* instance);
+
+void desktop_api_unlock(Desktop* instance);

+ 1 - 2
applications/services/desktop/desktop_settings.h

@@ -8,7 +8,7 @@
 #include <toolbox/saved_struct.h>
 #include <storage/storage.h>
 
-#define DESKTOP_SETTINGS_VER (6)
+#define DESKTOP_SETTINGS_VER (7)
 
 #define DESKTOP_SETTINGS_PATH INT_PATH(DESKTOP_SETTINGS_FILE_NAME)
 #define DESKTOP_SETTINGS_MAGIC (0x17)
@@ -52,7 +52,6 @@ typedef struct {
     FavoriteApp favorite_primary;
     FavoriteApp favorite_secondary;
     PinCode pin_code;
-    uint8_t is_locked;
     uint32_t auto_lock_delay_ms;
     uint8_t dummy_mode;
 } DesktopSettings;

+ 74 - 0
applications/services/desktop/helpers/pin.c

@@ -0,0 +1,74 @@
+#include "pin.h"
+
+#include <notification/notification.h>
+#include <notification/notification_messages.h>
+#include <stddef.h>
+#include <furi.h>
+#include <furi_hal.h>
+#include <gui/gui.h>
+
+#include "../desktop_i.h"
+
+static const NotificationSequence sequence_pin_fail = {
+    &message_display_backlight_on,
+
+    &message_red_255,
+    &message_vibro_on,
+    &message_delay_100,
+    &message_vibro_off,
+    &message_red_0,
+
+    &message_delay_250,
+
+    &message_red_255,
+    &message_vibro_on,
+    &message_delay_100,
+    &message_vibro_off,
+    &message_red_0,
+    NULL,
+};
+
+static const uint8_t desktop_helpers_fails_timeout[] = {
+    0,
+    0,
+    0,
+    0,
+    30,
+    60,
+    90,
+    120,
+    150,
+    180,
+    /* +60 for every next fail */
+};
+
+void desktop_pin_lock_error_notify() {
+    NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
+    notification_message(notification, &sequence_pin_fail);
+    furi_record_close(RECORD_NOTIFICATION);
+}
+
+uint32_t desktop_pin_lock_get_fail_timeout() {
+    uint32_t pin_fails = furi_hal_rtc_get_pin_fails();
+    uint32_t pin_timeout = 0;
+    uint32_t max_index = COUNT_OF(desktop_helpers_fails_timeout) - 1;
+    if(pin_fails <= max_index) {
+        pin_timeout = desktop_helpers_fails_timeout[pin_fails];
+    } else {
+        pin_timeout = desktop_helpers_fails_timeout[max_index] + (pin_fails - max_index) * 60;
+    }
+
+    return pin_timeout;
+}
+
+bool desktop_pin_compare(const PinCode* pin_code1, const PinCode* pin_code2) {
+    furi_assert(pin_code1);
+    furi_assert(pin_code2);
+    bool result = false;
+
+    if(pin_code1->length == pin_code2->length) {
+        result = !memcmp(pin_code1->data, pin_code2->data, pin_code1->length);
+    }
+
+    return result;
+}

+ 11 - 0
applications/services/desktop/helpers/pin.h

@@ -0,0 +1,11 @@
+#pragma once
+#include <stdbool.h>
+#include <stdint.h>
+#include "../desktop.h"
+#include <desktop/desktop_settings.h>
+
+void desktop_pin_lock_error_notify();
+
+uint32_t desktop_pin_lock_get_fail_timeout();
+
+bool desktop_pin_compare(const PinCode* pin_code1, const PinCode* pin_code2);

+ 0 - 140
applications/services/desktop/helpers/pin_lock.c

@@ -1,140 +0,0 @@
-
-#include <notification/notification.h>
-#include <notification/notification_messages.h>
-#include <stddef.h>
-#include <furi.h>
-#include <furi_hal.h>
-#include <gui/gui.h>
-
-#include "../helpers/pin_lock.h"
-#include "../desktop_i.h"
-#include <cli/cli.h>
-#include <cli/cli_vcp.h>
-
-static const NotificationSequence sequence_pin_fail = {
-    &message_display_backlight_on,
-
-    &message_red_255,
-    &message_vibro_on,
-    &message_delay_100,
-    &message_vibro_off,
-    &message_red_0,
-
-    &message_delay_250,
-
-    &message_red_255,
-    &message_vibro_on,
-    &message_delay_100,
-    &message_vibro_off,
-    &message_red_0,
-    NULL,
-};
-
-static const uint8_t desktop_helpers_fails_timeout[] = {
-    0,
-    0,
-    0,
-    0,
-    30,
-    60,
-    90,
-    120,
-    150,
-    180,
-    /* +60 for every next fail */
-};
-
-void desktop_pin_lock_error_notify() {
-    NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
-    notification_message(notification, &sequence_pin_fail);
-    furi_record_close(RECORD_NOTIFICATION);
-}
-
-uint32_t desktop_pin_lock_get_fail_timeout() {
-    uint32_t pin_fails = furi_hal_rtc_get_pin_fails();
-    uint32_t pin_timeout = 0;
-    uint32_t max_index = COUNT_OF(desktop_helpers_fails_timeout) - 1;
-    if(pin_fails <= max_index) {
-        pin_timeout = desktop_helpers_fails_timeout[pin_fails];
-    } else {
-        pin_timeout = desktop_helpers_fails_timeout[max_index] + (pin_fails - max_index) * 60;
-    }
-
-    return pin_timeout;
-}
-
-void desktop_pin_lock(DesktopSettings* settings) {
-    furi_assert(settings);
-
-    furi_hal_rtc_set_pin_fails(0);
-    furi_hal_rtc_set_flag(FuriHalRtcFlagLock);
-    Cli* cli = furi_record_open(RECORD_CLI);
-    cli_session_close(cli);
-    furi_record_close(RECORD_CLI);
-    settings->is_locked = 1;
-    DESKTOP_SETTINGS_SAVE(settings);
-}
-
-void desktop_pin_unlock(DesktopSettings* settings) {
-    furi_assert(settings);
-
-    furi_hal_rtc_reset_flag(FuriHalRtcFlagLock);
-    Cli* cli = furi_record_open(RECORD_CLI);
-    cli_session_open(cli, &cli_vcp);
-    furi_record_close(RECORD_CLI);
-    settings->is_locked = 0;
-    DESKTOP_SETTINGS_SAVE(settings);
-}
-
-void desktop_pin_lock_init(DesktopSettings* settings) {
-    furi_assert(settings);
-
-    if(settings->pin_code.length > 0) {
-        if(settings->is_locked == 1) {
-            furi_hal_rtc_set_flag(FuriHalRtcFlagLock);
-        } else {
-            if(desktop_pin_lock_is_locked()) {
-                settings->is_locked = 1;
-                DESKTOP_SETTINGS_SAVE(settings);
-            }
-        }
-    } else {
-        furi_hal_rtc_set_pin_fails(0);
-        furi_hal_rtc_reset_flag(FuriHalRtcFlagLock);
-    }
-
-    if(desktop_pin_lock_is_locked()) {
-        Cli* cli = furi_record_open(RECORD_CLI);
-        cli_session_close(cli);
-        furi_record_close(RECORD_CLI);
-    }
-}
-
-bool desktop_pin_lock_verify(const PinCode* pin_set, const PinCode* pin_entered) {
-    bool result = false;
-    if(desktop_pins_are_equal(pin_set, pin_entered)) {
-        furi_hal_rtc_set_pin_fails(0);
-        result = true;
-    } else {
-        uint32_t pin_fails = furi_hal_rtc_get_pin_fails();
-        furi_hal_rtc_set_pin_fails(pin_fails + 1);
-        result = false;
-    }
-    return result;
-}
-
-bool desktop_pin_lock_is_locked() {
-    return furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock);
-}
-
-bool desktop_pins_are_equal(const PinCode* pin_code1, const PinCode* pin_code2) {
-    furi_assert(pin_code1);
-    furi_assert(pin_code2);
-    bool result = false;
-
-    if(pin_code1->length == pin_code2->length) {
-        result = !memcmp(pin_code1->data, pin_code2->data, pin_code1->length);
-    }
-
-    return result;
-}

+ 0 - 21
applications/services/desktop/helpers/pin_lock.h

@@ -1,21 +0,0 @@
-#pragma once
-#include <stdbool.h>
-#include <stdint.h>
-#include "../desktop.h"
-#include <desktop/desktop_settings.h>
-
-void desktop_pin_lock_error_notify();
-
-uint32_t desktop_pin_lock_get_fail_timeout();
-
-void desktop_pin_lock(DesktopSettings* settings);
-
-void desktop_pin_unlock(DesktopSettings* settings);
-
-bool desktop_pin_lock_is_locked();
-
-void desktop_pin_lock_init(DesktopSettings* settings);
-
-bool desktop_pin_lock_verify(const PinCode* pin_set, const PinCode* pin_entered);
-
-bool desktop_pins_are_equal(const PinCode* pin_code1, const PinCode* pin_code2);

+ 1 - 18
applications/services/desktop/scenes/desktop_scene_lock_menu.c

@@ -10,7 +10,7 @@
 #include "../views/desktop_view_lock_menu.h"
 #include "desktop_scene_i.h"
 #include "desktop_scene.h"
-#include "../helpers/pin_lock.h"
+#include "../helpers/pin.h"
 
 #define TAG "DesktopSceneLock"
 
@@ -25,7 +25,6 @@ void desktop_scene_lock_menu_on_enter(void* context) {
     DESKTOP_SETTINGS_LOAD(&desktop->settings);
     scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0);
     desktop_lock_menu_set_callback(desktop->lock_menu, desktop_scene_lock_menu_callback, desktop);
-    desktop_lock_menu_set_pin_state(desktop->lock_menu, desktop->settings.pin_code.length > 0);
     desktop_lock_menu_set_dummy_mode_state(desktop->lock_menu, desktop->settings.dummy_mode);
     desktop_lock_menu_set_stealth_mode_state(
         desktop->lock_menu, furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode));
@@ -44,7 +43,6 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) {
         if(check_pin_changed) {
             DESKTOP_SETTINGS_LOAD(&desktop->settings);
             if(desktop->settings.pin_code.length > 0) {
-                desktop_lock_menu_set_pin_state(desktop->lock_menu, true);
                 scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0);
             }
         }
@@ -55,21 +53,6 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) {
             desktop_lock(desktop);
             consumed = true;
             break;
-        case DesktopLockMenuEventPinLock:
-            if(desktop->settings.pin_code.length > 0) {
-                desktop_pin_lock(&desktop->settings);
-                desktop_lock(desktop);
-            } else {
-                LoaderStatus status =
-                    loader_start(desktop->loader, "Desktop", DESKTOP_SETTINGS_RUN_PIN_SETUP_ARG);
-                if(status == LoaderStatusOk) {
-                    scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 1);
-                } else {
-                    FURI_LOG_E(TAG, "Unable to start desktop settings");
-                }
-            }
-            consumed = true;
-            break;
         case DesktopLockMenuEventDummyModeOn:
             desktop_set_dummy_mode_state(desktop, true);
             scene_manager_search_and_switch_to_previous_scene(

+ 2 - 2
applications/services/desktop/scenes/desktop_scene_locked.c

@@ -7,7 +7,7 @@
 
 #include "../desktop.h"
 #include "../desktop_i.h"
-#include "../helpers/pin_lock.h"
+#include "../helpers/pin.h"
 #include "../animations/animation_manager.h"
 #include "../views/desktop_events.h"
 #include "../views/desktop_view_pin_input.h"
@@ -45,7 +45,7 @@ void desktop_scene_locked_on_enter(void* context) {
     bool switch_to_timeout_scene = false;
     uint32_t state = scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneLocked);
     if(state == SCENE_LOCKED_FIRST_ENTER) {
-        bool pin_locked = desktop_pin_lock_is_locked();
+        bool pin_locked = desktop->settings.pin_code.length > 0;
         view_port_enabled_set(desktop->lock_icon_viewport, true);
         Gui* gui = furi_record_open(RECORD_GUI);
         gui_set_lockdown(gui, true);

+ 5 - 3
applications/services/desktop/scenes/desktop_scene_main.c

@@ -106,10 +106,12 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) {
 
     if(event.type == SceneManagerEventTypeCustom) {
         switch(event.event) {
-        case DesktopMainEventOpenMenu:
-            loader_show_menu();
+        case DesktopMainEventOpenMenu: {
+            Loader* loader = furi_record_open(RECORD_LOADER);
+            loader_show_menu(loader);
+            furi_record_close(RECORD_LOADER);
             consumed = true;
-            break;
+        } break;
 
         case DesktopMainEventOpenLockMenu:
             scene_manager_next_scene(desktop->scene_manager, DesktopSceneLockMenu);

+ 4 - 3
applications/services/desktop/scenes/desktop_scene_pin_input.c

@@ -12,7 +12,7 @@
 #include "../animations/animation_manager.h"
 #include "../views/desktop_events.h"
 #include "../views/desktop_view_pin_input.h"
-#include "../helpers/pin_lock.h"
+#include "../helpers/pin.h"
 #include "desktop_scene.h"
 #include "desktop_scene_i.h"
 
@@ -54,9 +54,11 @@ static void desktop_scene_pin_input_back_callback(void* context) {
 
 static void desktop_scene_pin_input_done_callback(const PinCode* pin_code, void* context) {
     Desktop* desktop = (Desktop*)context;
-    if(desktop_pin_lock_verify(&desktop->settings.pin_code, pin_code)) {
+    if(desktop_pin_compare(&desktop->settings.pin_code, pin_code)) {
         view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopPinInputEventUnlocked);
     } else {
+        uint32_t pin_fails = furi_hal_rtc_get_pin_fails();
+        furi_hal_rtc_set_pin_fails(pin_fails + 1);
         view_dispatcher_send_custom_event(
             desktop->view_dispatcher, DesktopPinInputEventUnlockFailed);
     }
@@ -126,7 +128,6 @@ bool desktop_scene_pin_input_on_event(void* context, SceneManagerEvent event) {
             consumed = true;
             break;
         case DesktopPinInputEventUnlocked:
-            desktop_pin_unlock(&desktop->settings);
             desktop_unlock(desktop);
             consumed = true;
             break;

+ 0 - 1
applications/services/desktop/views/desktop_events.h

@@ -31,7 +31,6 @@ typedef enum {
     DesktopDebugEventExit,
 
     DesktopLockMenuEventLock,
-    DesktopLockMenuEventPinLock,
     DesktopLockMenuEventDummyModeOn,
     DesktopLockMenuEventDummyModeOff,
     DesktopLockMenuEventStealthModeOn,

+ 5 - 2
applications/services/desktop/views/desktop_view_debug.c

@@ -65,13 +65,16 @@ void desktop_debug_render(Canvas* canvas, void* model) {
             version_get_builddate(ver));
         canvas_draw_str(canvas, 0, 30 + STATUS_BAR_Y_SHIFT, buffer);
 
+        uint16_t api_major, api_minor;
+        furi_hal_info_get_api_version(&api_major, &api_minor);
         snprintf(
             buffer,
             sizeof(buffer),
-            "%s%s [%s] %s",
+            "%s%s [%d.%d] %s",
             version_get_dirty_flag(ver) ? "[!] " : "",
             version_get_githash(ver),
-            version_get_gitbranchnum(ver),
+            api_major,
+            api_minor,
             c2_ver ? c2_ver->StackTypeString : "<none>");
         canvas_draw_str(canvas, 0, 40 + STATUS_BAR_Y_SHIFT, buffer);
 

+ 1 - 13
applications/services/desktop/views/desktop_view_lock_menu.c

@@ -23,14 +23,6 @@ void desktop_lock_menu_set_callback(
     lock_menu->context = context;
 }
 
-void desktop_lock_menu_set_pin_state(DesktopLockMenuView* lock_menu, bool pin_is_set) {
-    with_view_model(
-        lock_menu->view,
-        DesktopLockMenuViewModel * model,
-        { model->pin_is_set = pin_is_set; },
-        true);
-}
-
 void desktop_lock_menu_set_dummy_mode_state(DesktopLockMenuView* lock_menu, bool dummy_mode) {
     with_view_model(
         lock_menu->view,
@@ -102,7 +94,6 @@ bool desktop_lock_menu_input_callback(InputEvent* event, void* context) {
     bool consumed = false;
     bool dummy_mode = false;
     bool stealth_mode = false;
-    bool pin_is_set = false;
     bool update = false;
 
     with_view_model(
@@ -131,15 +122,12 @@ bool desktop_lock_menu_input_callback(InputEvent* event, void* context) {
             idx = model->idx;
             dummy_mode = model->dummy_mode;
             stealth_mode = model->stealth_mode;
-            pin_is_set = model->pin_is_set;
         },
         update);
 
     if(event->key == InputKeyOk) {
         if((idx == DesktopLockMenuIndexLock)) {
-            if((pin_is_set) && (event->type == InputTypeShort)) {
-                lock_menu->callback(DesktopLockMenuEventPinLock, lock_menu->context);
-            } else if((pin_is_set == false) && (event->type == InputTypeShort)) {
+            if((event->type == InputTypeShort)) {
                 lock_menu->callback(DesktopLockMenuEventLock, lock_menu->context);
             }
         } else if(idx == DesktopLockMenuIndexStealth) {

+ 0 - 2
applications/services/desktop/views/desktop_view_lock_menu.h

@@ -17,7 +17,6 @@ struct DesktopLockMenuView {
 
 typedef struct {
     uint8_t idx;
-    bool pin_is_set;
     bool dummy_mode;
     bool stealth_mode;
 } DesktopLockMenuViewModel;
@@ -28,7 +27,6 @@ void desktop_lock_menu_set_callback(
     void* context);
 
 View* desktop_lock_menu_get_view(DesktopLockMenuView* lock_menu);
-void desktop_lock_menu_set_pin_state(DesktopLockMenuView* lock_menu, bool pin_is_set);
 void desktop_lock_menu_set_dummy_mode_state(DesktopLockMenuView* lock_menu, bool dummy_mode);
 void desktop_lock_menu_set_stealth_mode_state(DesktopLockMenuView* lock_menu, bool stealth_mode);
 void desktop_lock_menu_set_idx(DesktopLockMenuView* lock_menu, uint8_t idx);

+ 0 - 79
applications/services/desktop/views/desktop_view_pin_setup_done.c

@@ -1,79 +0,0 @@
-#include <furi.h>
-#include <furi_hal.h>
-#include <gui/elements.h>
-#include <gui/canvas.h>
-#include <toolbox/version.h>
-#include <assets_icons.h>
-#include <dolphin/helpers/dolphin_state.h>
-#include <dolphin/dolphin.h>
-
-#include "../desktop_i.h"
-#include "desktop_view_pin_setup_done.h"
-
-struct DesktopViewPinSetupDone {
-    View* view;
-    DesktopViewPinSetupDoneDoneCallback callback;
-    void* context;
-};
-
-static void desktop_view_pin_done_draw(Canvas* canvas, void* model) {
-    furi_assert(canvas);
-    UNUSED(model);
-
-    canvas_set_font(canvas, FontPrimary);
-    elements_multiline_text_aligned(
-        canvas, 64, 0, AlignCenter, AlignTop, "Prepare to use\narrows as\nPIN symbols");
-
-    canvas_set_font(canvas, FontSecondary);
-    elements_multiline_text(canvas, 58, 24, "Prepare to use\narrows as\nPIN symbols");
-
-    canvas_draw_icon(canvas, 16, 18, &I_Pin_attention_dpad_29x29);
-    elements_button_right(canvas, "Next");
-}
-
-static bool desktop_view_pin_done_input(InputEvent* event, void* context) {
-    furi_assert(event);
-    furi_assert(context);
-
-    DesktopViewPinSetupDone* instance = context;
-    bool consumed = false;
-
-    if((event->key == InputKeyRight) && (event->type == InputTypeShort)) {
-        instance->callback(instance->context);
-        consumed = true;
-    }
-
-    return consumed;
-}
-
-void desktop_view_pin_done_set_callback(
-    DesktopViewPinSetupDone* instance,
-    DesktopViewPinSetupDoneDoneCallback callback,
-    void* context) {
-    furi_assert(instance);
-    furi_assert(callback);
-    instance->callback = callback;
-    instance->context = context;
-}
-
-DesktopViewPinSetupDone* desktop_view_pin_done_alloc() {
-    DesktopViewPinSetupDone* view = malloc(sizeof(DesktopViewPinSetupDone));
-    view->view = view_alloc();
-    view_set_context(view->view, view);
-    view_set_draw_callback(view->view, desktop_view_pin_done_draw);
-    view_set_input_callback(view->view, desktop_view_pin_done_input);
-
-    return view;
-}
-
-void desktop_view_pin_done_free(DesktopViewPinSetupDone* instance) {
-    furi_assert(instance);
-
-    view_free(instance->view);
-    free(instance);
-}
-
-View* desktop_view_pin_done_get_view(DesktopViewPinSetupDone* instance) {
-    furi_assert(instance);
-    return instance->view;
-}

+ 0 - 15
applications/services/desktop/views/desktop_view_pin_setup_done.h

@@ -1,15 +0,0 @@
-#pragma once
-
-#include <gui/view.h>
-
-typedef struct DesktopViewPinSetupDone DesktopViewPinSetupDone;
-
-typedef void (*DesktopViewPinSetupDoneDoneCallback)(void*);
-
-void desktop_view_pin_done_set_callback(
-    DesktopViewPinSetupDone* instance,
-    DesktopViewPinSetupDoneDoneCallback callback,
-    void* context);
-DesktopViewPinSetupDone* desktop_view_pin_done_alloc();
-void desktop_view_pin_done_free(DesktopViewPinSetupDone* instance);
-View* desktop_view_pin_done_get_view(DesktopViewPinSetupDone* instance);

+ 2 - 0
applications/services/gui/modules/menu.c

@@ -154,6 +154,8 @@ Menu* menu_alloc() {
 void menu_free(Menu* menu) {
     furi_assert(menu);
     menu_reset(menu);
+    with_view_model(
+        menu->view, MenuModel * model, { MenuItemArray_clear(model->items); }, false);
     view_free(menu->view);
     free(menu);
 }

+ 0 - 4
applications/services/gui/view.c

@@ -19,19 +19,16 @@ void view_tie_icon_animation(View* view, IconAnimation* icon_animation) {
 
 void view_set_draw_callback(View* view, ViewDrawCallback callback) {
     furi_assert(view);
-    furi_assert(view->draw_callback == NULL);
     view->draw_callback = callback;
 }
 
 void view_set_input_callback(View* view, ViewInputCallback callback) {
     furi_assert(view);
-    furi_assert(view->input_callback == NULL);
     view->input_callback = callback;
 }
 
 void view_set_custom_callback(View* view, ViewCustomCallback callback) {
     furi_assert(view);
-    furi_assert(callback);
     view->custom_callback = callback;
 }
 
@@ -62,7 +59,6 @@ void view_set_update_callback_context(View* view, void* context) {
 
 void view_set_context(View* view, void* context) {
     furi_assert(view);
-    furi_assert(context);
     view->context = context;
 }
 

+ 9 - 0
applications/services/loader/application.fam

@@ -5,6 +5,7 @@ App(
     entry_point="loader_srv",
     cdefines=["SRV_LOADER"],
     requires=["gui"],
+    provides=["loader_start"],
     stack_size=2 * 1024,
     order=90,
     sdk_headers=[
@@ -12,3 +13,11 @@ App(
         "firmware_api/firmware_api.h",
     ],
 )
+
+App(
+    appid="loader_start",
+    apptype=FlipperAppType.STARTUP,
+    entry_point="loader_on_system_start",
+    requires=["loader"],
+    order=90,
+)

+ 7 - 0
applications/services/loader/firmware_api/firmware_api.cpp

@@ -6,6 +6,8 @@
 /* Generated table */
 #include <firmware_api_table.h>
 
+#include <furi_hal_info.h>
+
 static_assert(!has_hash_collisions(elf_api_table), "Detected API method hash collision!");
 
 constexpr HashtableApiInterface elf_api_interface{
@@ -19,3 +21,8 @@ constexpr HashtableApiInterface elf_api_interface{
 };
 
 const ElfApiInterface* const firmware_api_interface = &elf_api_interface;
+
+extern "C" void furi_hal_info_get_api_version(uint16_t* major, uint16_t* minor) {
+    *major = elf_api_interface.api_version_major;
+    *minor = elf_api_interface.api_version_minor;
+}

+ 207 - 347
applications/services/loader/loader.c

@@ -1,76 +1,114 @@
-#include "applications.h"
-#include <furi.h>
-#include "loader/loader.h"
+#include "loader.h"
 #include "loader_i.h"
+#include "loader_menu.h"
+#include <applications.h>
+#include <furi_hal.h>
+
+#define TAG "Loader"
+#define LOADER_MAGIC_THREAD_VALUE 0xDEADBEEF
+// api
+
+LoaderStatus loader_start(Loader* loader, const char* name, const char* args) {
+    LoaderMessage message;
+    LoaderMessageLoaderStatusResult result;
+
+    message.type = LoaderMessageTypeStartByName;
+    message.start.name = name;
+    message.start.args = args;
+    message.api_lock = api_lock_alloc_locked();
+    message.status_value = &result;
+    furi_message_queue_put(loader->queue, &message, FuriWaitForever);
+    api_lock_wait_unlock_and_free(message.api_lock);
+    return result.value;
+}
 
-#define TAG "LoaderSrv"
-
-#define LOADER_THREAD_FLAG_SHOW_MENU (1 << 0)
-#define LOADER_THREAD_FLAG_ALL (LOADER_THREAD_FLAG_SHOW_MENU)
-
-static Loader* loader_instance = NULL;
+bool loader_lock(Loader* loader) {
+    LoaderMessage message;
+    LoaderMessageBoolResult result;
+    message.type = LoaderMessageTypeLock;
+    message.api_lock = api_lock_alloc_locked();
+    message.bool_value = &result;
+    furi_message_queue_put(loader->queue, &message, FuriWaitForever);
+    api_lock_wait_unlock_and_free(message.api_lock);
+    return result.value;
+}
 
-static bool
-    loader_start_application(const FlipperApplication* application, const char* arguments) {
-    loader_instance->application = application;
+void loader_unlock(Loader* loader) {
+    LoaderMessage message;
+    message.type = LoaderMessageTypeUnlock;
+    furi_message_queue_put(loader->queue, &message, FuriWaitForever);
+}
 
-    furi_assert(loader_instance->application_arguments == NULL);
-    if(arguments && strlen(arguments) > 0) {
-        loader_instance->application_arguments = strdup(arguments);
-    }
+bool loader_is_locked(Loader* loader) {
+    LoaderMessage message;
+    LoaderMessageBoolResult result;
+    message.type = LoaderMessageTypeIsLocked;
+    message.api_lock = api_lock_alloc_locked();
+    message.bool_value = &result;
+    furi_message_queue_put(loader->queue, &message, FuriWaitForever);
+    api_lock_wait_unlock_and_free(message.api_lock);
+    return result.value;
+}
 
-    FURI_LOG_I(TAG, "Starting: %s", loader_instance->application->name);
+void loader_show_menu(Loader* loader) {
+    LoaderMessage message;
+    message.type = LoaderMessageTypeShowMenu;
+    furi_message_queue_put(loader->queue, &message, FuriWaitForever);
+}
 
-    FuriHalRtcHeapTrackMode mode = furi_hal_rtc_get_heap_track_mode();
-    if(mode > FuriHalRtcHeapTrackModeNone) {
-        furi_thread_enable_heap_trace(loader_instance->application_thread);
-    } else {
-        furi_thread_disable_heap_trace(loader_instance->application_thread);
-    }
+FuriPubSub* loader_get_pubsub(Loader* loader) {
+    furi_assert(loader);
+    // it's safe to return pubsub without locking
+    // because it's never freed and loader is never exited
+    // also the loader instance cannot be obtained until the pubsub is created
+    return loader->pubsub;
+}
 
-    furi_thread_set_name(loader_instance->application_thread, loader_instance->application->name);
-    furi_thread_set_appid(
-        loader_instance->application_thread, loader_instance->application->appid);
-    furi_thread_set_stack_size(
-        loader_instance->application_thread, loader_instance->application->stack_size);
-    furi_thread_set_context(
-        loader_instance->application_thread, loader_instance->application_arguments);
-    furi_thread_set_callback(
-        loader_instance->application_thread, loader_instance->application->app);
+// callbacks
 
-    furi_thread_start(loader_instance->application_thread);
+static void loader_menu_closed_callback(void* context) {
+    Loader* loader = context;
+    LoaderMessage message;
+    message.type = LoaderMessageTypeMenuClosed;
+    furi_message_queue_put(loader->queue, &message, FuriWaitForever);
+}
 
-    return true;
+static void loader_menu_click_callback(const char* name, void* context) {
+    Loader* loader = context;
+    loader_start(loader, name, NULL);
 }
 
-static void loader_menu_callback(void* _ctx, uint32_t index) {
-    UNUSED(index);
-    const FlipperApplication* application = _ctx;
+static void loader_thread_state_callback(FuriThreadState thread_state, void* context) {
+    furi_assert(context);
 
-    furi_assert(application->app);
-    furi_assert(application->name);
+    Loader* loader = context;
+    LoaderEvent event;
 
-    if(!loader_lock(loader_instance)) {
-        FURI_LOG_E(TAG, "Loader is locked");
-        return;
-    }
+    if(thread_state == FuriThreadStateRunning) {
+        event.type = LoaderEventTypeApplicationStarted;
+        furi_pubsub_publish(loader->pubsub, &event);
+    } else if(thread_state == FuriThreadStateStopped) {
+        LoaderMessage message;
+        message.type = LoaderMessageTypeAppClosed;
+        furi_message_queue_put(loader->queue, &message, FuriWaitForever);
 
-    loader_start_application(application, NULL);
+        event.type = LoaderEventTypeApplicationStopped;
+        furi_pubsub_publish(loader->pubsub, &event);
+    }
 }
 
-static void loader_submenu_callback(void* context, uint32_t index) {
-    UNUSED(index);
-    uint32_t view_id = (uint32_t)context;
-    view_dispatcher_switch_to_view(loader_instance->view_dispatcher, view_id);
-}
+// implementation
 
-static void loader_cli_print_usage() {
-    printf("Usage:\r\n");
-    printf("loader <cmd> <args>\r\n");
-    printf("Cmd list:\r\n");
-    printf("\tlist\t - List available applications\r\n");
-    printf("\topen <Application Name:string>\t - Open application by name\r\n");
-    printf("\tinfo\t - Show loader state\r\n");
+static Loader* loader_alloc() {
+    Loader* loader = malloc(sizeof(Loader));
+    loader->pubsub = furi_pubsub_alloc();
+    loader->queue = furi_message_queue_alloc(1, sizeof(LoaderMessage));
+    loader->loader_menu = NULL;
+    loader->app.args = NULL;
+    loader->app.name = NULL;
+    loader->app.thread = NULL;
+    loader->app.insomniac = false;
+    return loader;
 }
 
 static FlipperApplication const* loader_find_application_by_name_in_list(
@@ -85,7 +123,7 @@ static FlipperApplication const* loader_find_application_by_name_in_list(
     return NULL;
 }
 
-const FlipperApplication* loader_find_application_by_name(const char* name) {
+static const FlipperApplication* loader_find_application_by_name(const char* name) {
     const FlipperApplication* application = NULL;
     application = loader_find_application_by_name_in_list(name, FLIPPER_APPS, FLIPPER_APPS_COUNT);
     if(!application) {
@@ -100,346 +138,168 @@ const FlipperApplication* loader_find_application_by_name(const char* name) {
     return application;
 }
 
-static void loader_cli_open(Cli* cli, FuriString* args, Loader* instance) {
-    UNUSED(cli);
-    if(loader_is_locked(instance)) {
-        if(instance->application) {
-            furi_assert(instance->application->name);
-            printf("Can't start, %s application is running", instance->application->name);
-        } else {
-            printf("Can't start, furi application is running");
-        }
-        return;
-    }
-
-    FuriString* application_name;
-    application_name = furi_string_alloc();
-
-    do {
-        if(!args_read_probably_quoted_string_and_trim(args, application_name)) {
-            printf("No application provided\r\n");
-            break;
-        }
+static void
+    loader_start_internal_app(Loader* loader, const FlipperApplication* app, const char* args) {
+    FURI_LOG_I(TAG, "Starting %s", app->name);
 
-        const FlipperApplication* application =
-            loader_find_application_by_name(furi_string_get_cstr(application_name));
-        if(!application) {
-            printf("%s doesn't exists\r\n", furi_string_get_cstr(application_name));
-            break;
-        }
+    // store args
+    furi_assert(loader->app.args == NULL);
+    if(args && strlen(args) > 0) {
+        loader->app.args = strdup(args);
+    }
 
-        furi_string_trim(args);
-        if(!loader_start_application(application, furi_string_get_cstr(args))) {
-            printf("Can't start, furi application is running");
-            return;
-        } else {
-            // We must to increment lock counter to keep balance
-            // TODO: rewrite whole thing, it's complex as hell
-            FURI_CRITICAL_ENTER();
-            instance->lock_count++;
-            FURI_CRITICAL_EXIT();
-        }
-    } while(false);
+    // store name
+    furi_assert(loader->app.name == NULL);
+    loader->app.name = strdup(app->name);
 
-    furi_string_free(application_name);
-}
+    // setup app thread
+    loader->app.thread =
+        furi_thread_alloc_ex(app->name, app->stack_size, app->app, loader->app.args);
+    furi_thread_set_appid(loader->app.thread, app->appid);
 
-static void loader_cli_list(Cli* cli, FuriString* args, Loader* instance) {
-    UNUSED(cli);
-    UNUSED(args);
-    UNUSED(instance);
-    printf("Applications:\r\n");
-    for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) {
-        printf("\t%s\r\n", FLIPPER_APPS[i].name);
+    // setup heap trace
+    FuriHalRtcHeapTrackMode mode = furi_hal_rtc_get_heap_track_mode();
+    if(mode > FuriHalRtcHeapTrackModeNone) {
+        furi_thread_enable_heap_trace(loader->app.thread);
+    } else {
+        furi_thread_disable_heap_trace(loader->app.thread);
     }
-}
 
-static void loader_cli_info(Cli* cli, FuriString* args, Loader* instance) {
-    UNUSED(cli);
-    UNUSED(args);
-    if(!loader_is_locked(instance)) {
-        printf("No application is running\r\n");
+    // setup insomnia
+    if(!(app->flags & FlipperApplicationFlagInsomniaSafe)) {
+        furi_hal_power_insomnia_enter();
+        loader->app.insomniac = true;
     } else {
-        printf("Running application: ");
-        if(instance->application) {
-            furi_assert(instance->application->name);
-            printf("%s\r\n", instance->application->name);
-        } else {
-            printf("unknown\r\n");
-        }
+        loader->app.insomniac = false;
     }
-}
 
-static void loader_cli(Cli* cli, FuriString* args, void* _ctx) {
-    furi_assert(_ctx);
-    Loader* instance = _ctx;
+    // setup app thread callbacks
+    furi_thread_set_state_context(loader->app.thread, loader);
+    furi_thread_set_state_callback(loader->app.thread, loader_thread_state_callback);
 
-    FuriString* cmd;
-    cmd = furi_string_alloc();
-
-    do {
-        if(!args_read_string_and_trim(args, cmd)) {
-            loader_cli_print_usage();
-            break;
-        }
-
-        if(furi_string_cmp_str(cmd, "list") == 0) {
-            loader_cli_list(cli, args, instance);
-            break;
-        }
-
-        if(furi_string_cmp_str(cmd, "open") == 0) {
-            loader_cli_open(cli, args, instance);
-            break;
-        }
-
-        if(furi_string_cmp_str(cmd, "info") == 0) {
-            loader_cli_info(cli, args, instance);
-            break;
-        }
-
-        loader_cli_print_usage();
-    } while(false);
-
-    furi_string_free(cmd);
+    // start app thread
+    furi_thread_start(loader->app.thread);
 }
 
-LoaderStatus loader_start(Loader* instance, const char* name, const char* args) {
-    UNUSED(instance);
-    furi_assert(name);
+// process messages
 
-    const FlipperApplication* application = loader_find_application_by_name(name);
-
-    if(!application) {
-        FURI_LOG_E(TAG, "Can't find application with name %s", name);
-        return LoaderStatusErrorUnknownApp;
+static void loader_do_menu_show(Loader* loader) {
+    if(!loader->loader_menu) {
+        loader->loader_menu = loader_menu_alloc();
+        loader_menu_set_closed_callback(loader->loader_menu, loader_menu_closed_callback, loader);
+        loader_menu_set_click_callback(loader->loader_menu, loader_menu_click_callback, loader);
+        loader_menu_start(loader->loader_menu);
     }
-
-    if(!loader_lock(loader_instance)) {
-        FURI_LOG_E(TAG, "Loader is locked");
-        return LoaderStatusErrorAppStarted;
-    }
-
-    if(!loader_start_application(application, args)) {
-        return LoaderStatusErrorInternal;
-    }
-
-    return LoaderStatusOk;
 }
 
-bool loader_lock(Loader* instance) {
-    FURI_CRITICAL_ENTER();
-    bool result = false;
-    if(instance->lock_count == 0) {
-        instance->lock_count++;
-        result = true;
+static void loader_do_menu_closed(Loader* loader) {
+    if(loader->loader_menu) {
+        loader_menu_stop(loader->loader_menu);
+        loader_menu_free(loader->loader_menu);
+        loader->loader_menu = NULL;
     }
-    FURI_CRITICAL_EXIT();
-    return result;
 }
 
-void loader_unlock(Loader* instance) {
-    FURI_CRITICAL_ENTER();
-    if(instance->lock_count > 0) instance->lock_count--;
-    FURI_CRITICAL_EXIT();
+static bool loader_do_is_locked(Loader* loader) {
+    return loader->app.thread != NULL;
 }
 
-bool loader_is_locked(const Loader* instance) {
-    return instance->lock_count > 0;
-}
-
-static void loader_thread_state_callback(FuriThreadState thread_state, void* context) {
-    furi_assert(context);
-
-    Loader* instance = context;
-    LoaderEvent event;
-
-    if(thread_state == FuriThreadStateRunning) {
-        event.type = LoaderEventTypeApplicationStarted;
-        furi_pubsub_publish(loader_instance->pubsub, &event);
-
-        if(!(loader_instance->application->flags & FlipperApplicationFlagInsomniaSafe)) {
-            furi_hal_power_insomnia_enter();
-        }
-    } else if(thread_state == FuriThreadStateStopped) {
-        FURI_LOG_I(TAG, "Application stopped. Free heap: %zu", memmgr_get_free_heap());
-
-        if(loader_instance->application_arguments) {
-            free(loader_instance->application_arguments);
-            loader_instance->application_arguments = NULL;
-        }
-
-        if(!(loader_instance->application->flags & FlipperApplicationFlagInsomniaSafe)) {
-            furi_hal_power_insomnia_exit();
-        }
-        loader_unlock(instance);
-
-        event.type = LoaderEventTypeApplicationStopped;
-        furi_pubsub_publish(loader_instance->pubsub, &event);
+static LoaderStatus loader_do_start_by_name(Loader* loader, const char* name, const char* args) {
+    if(loader_do_is_locked(loader)) {
+        return LoaderStatusErrorAppStarted;
     }
-}
 
-static uint32_t loader_hide_menu(void* context) {
-    UNUSED(context);
-    return VIEW_NONE;
-}
+    const FlipperApplication* app = loader_find_application_by_name(name);
 
-static uint32_t loader_back_to_primary_menu(void* context) {
-    furi_assert(context);
-    Submenu* submenu = context;
-    submenu_set_selected_item(submenu, 0);
-    return LoaderMenuViewPrimary;
-}
+    if(!app) {
+        return LoaderStatusErrorUnknownApp;
+    }
 
-static Loader* loader_alloc() {
-    Loader* instance = malloc(sizeof(Loader));
-
-    instance->application_thread = furi_thread_alloc();
-
-    furi_thread_set_state_context(instance->application_thread, instance);
-    furi_thread_set_state_callback(instance->application_thread, loader_thread_state_callback);
-
-    instance->pubsub = furi_pubsub_alloc();
-
-#ifdef SRV_CLI
-    instance->cli = furi_record_open(RECORD_CLI);
-    cli_add_command(
-        instance->cli, RECORD_LOADER, CliCommandFlagParallelSafe, loader_cli, instance);
-#else
-    UNUSED(loader_cli);
-#endif
-
-    instance->loader_thread = furi_thread_get_current_id();
-
-    // Gui
-    instance->gui = furi_record_open(RECORD_GUI);
-    instance->view_dispatcher = view_dispatcher_alloc();
-    view_dispatcher_attach_to_gui(
-        instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen);
-    // Primary menu
-    instance->primary_menu = menu_alloc();
-    view_set_previous_callback(menu_get_view(instance->primary_menu), loader_hide_menu);
-    view_dispatcher_add_view(
-        instance->view_dispatcher, LoaderMenuViewPrimary, menu_get_view(instance->primary_menu));
-    // Settings menu
-    instance->settings_menu = submenu_alloc();
-    view_set_context(submenu_get_view(instance->settings_menu), instance->settings_menu);
-    view_set_previous_callback(
-        submenu_get_view(instance->settings_menu), loader_back_to_primary_menu);
-    view_dispatcher_add_view(
-        instance->view_dispatcher,
-        LoaderMenuViewSettings,
-        submenu_get_view(instance->settings_menu));
-
-    view_dispatcher_enable_queue(instance->view_dispatcher);
-
-    return instance;
+    loader_start_internal_app(loader, app, args);
+    return LoaderStatusOk;
 }
 
-static void loader_free(Loader* instance) {
-    furi_assert(instance);
-
-    if(instance->cli) {
-        furi_record_close(RECORD_CLI);
+static bool loader_do_lock(Loader* loader) {
+    if(loader->app.thread) {
+        return false;
     }
 
-    furi_pubsub_free(instance->pubsub);
-
-    furi_thread_free(instance->application_thread);
-
-    menu_free(loader_instance->primary_menu);
-    view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewPrimary);
-    submenu_free(loader_instance->settings_menu);
-    view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewSettings);
-    view_dispatcher_free(loader_instance->view_dispatcher);
-
-    furi_record_close(RECORD_GUI);
+    loader->app.thread = (FuriThread*)LOADER_MAGIC_THREAD_VALUE;
+    return true;
+}
 
-    free(instance);
-    instance = NULL;
+static void loader_do_unlock(Loader* loader) {
+    furi_assert(loader->app.thread == (FuriThread*)LOADER_MAGIC_THREAD_VALUE);
+    loader->app.thread = NULL;
 }
 
-static void loader_build_menu() {
-    FURI_LOG_I(TAG, "Building main menu");
-    size_t i;
-    for(i = 0; i < FLIPPER_APPS_COUNT; i++) {
-        menu_add_item(
-            loader_instance->primary_menu,
-            FLIPPER_APPS[i].name,
-            FLIPPER_APPS[i].icon,
-            i,
-            loader_menu_callback,
-            (void*)&FLIPPER_APPS[i]);
+static void loader_do_app_closed(Loader* loader) {
+    furi_assert(loader->app.thread);
+    FURI_LOG_I(TAG, "Application stopped. Free heap: %zu", memmgr_get_free_heap());
+    if(loader->app.args) {
+        free(loader->app.args);
+        loader->app.args = NULL;
     }
-    menu_add_item(
-        loader_instance->primary_menu,
-        "Settings",
-        &A_Settings_14,
-        i++,
-        loader_submenu_callback,
-        (void*)LoaderMenuViewSettings);
-}
 
-static void loader_build_submenu() {
-    FURI_LOG_I(TAG, "Building settings menu");
-    for(size_t i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) {
-        submenu_add_item(
-            loader_instance->settings_menu,
-            FLIPPER_SETTINGS_APPS[i].name,
-            i,
-            loader_menu_callback,
-            (void*)&FLIPPER_SETTINGS_APPS[i]);
+    if(loader->app.insomniac) {
+        furi_hal_power_insomnia_exit();
     }
-}
 
-void loader_show_menu() {
-    furi_assert(loader_instance);
-    furi_thread_flags_set(loader_instance->loader_thread, LOADER_THREAD_FLAG_SHOW_MENU);
-}
+    free(loader->app.name);
+    loader->app.name = NULL;
 
-void loader_update_menu() {
-    menu_reset(loader_instance->primary_menu);
-    loader_build_menu();
+    furi_thread_join(loader->app.thread);
+    furi_thread_free(loader->app.thread);
+    loader->app.thread = NULL;
 }
 
+// app
+
 int32_t loader_srv(void* p) {
     UNUSED(p);
+    Loader* loader = loader_alloc();
+    furi_record_create(RECORD_LOADER, loader);
+
     FURI_LOG_I(TAG, "Executing system start hooks");
     for(size_t i = 0; i < FLIPPER_ON_SYSTEM_START_COUNT; i++) {
         FLIPPER_ON_SYSTEM_START[i]();
     }
 
-    FURI_LOG_I(TAG, "Starting");
-    loader_instance = loader_alloc();
-
-    loader_build_menu();
-    loader_build_submenu();
-
-    FURI_LOG_I(TAG, "Started");
-
-    furi_record_create(RECORD_LOADER, loader_instance);
-
     if(FLIPPER_AUTORUN_APP_NAME && strlen(FLIPPER_AUTORUN_APP_NAME)) {
-        loader_start(loader_instance, FLIPPER_AUTORUN_APP_NAME, NULL);
+        loader_do_start_by_name(loader, FLIPPER_AUTORUN_APP_NAME, NULL);
     }
 
-    while(1) {
-        uint32_t flags =
-            furi_thread_flags_wait(LOADER_THREAD_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever);
-        if(flags & LOADER_THREAD_FLAG_SHOW_MENU) {
-            menu_set_selected_item(loader_instance->primary_menu, 0);
-            view_dispatcher_switch_to_view(
-                loader_instance->view_dispatcher, LoaderMenuViewPrimary);
-            view_dispatcher_run(loader_instance->view_dispatcher);
+    LoaderMessage message;
+    while(true) {
+        if(furi_message_queue_get(loader->queue, &message, FuriWaitForever) == FuriStatusOk) {
+            switch(message.type) {
+            case LoaderMessageTypeStartByName:
+                message.status_value->value =
+                    loader_do_start_by_name(loader, message.start.name, message.start.args);
+                api_lock_unlock(message.api_lock);
+                break;
+            case LoaderMessageTypeShowMenu:
+                loader_do_menu_show(loader);
+                break;
+            case LoaderMessageTypeMenuClosed:
+                loader_do_menu_closed(loader);
+                break;
+            case LoaderMessageTypeIsLocked:
+                message.bool_value->value = loader_do_is_locked(loader);
+                api_lock_unlock(message.api_lock);
+                break;
+            case LoaderMessageTypeAppClosed:
+                loader_do_app_closed(loader);
+                break;
+            case LoaderMessageTypeLock:
+                message.bool_value->value = loader_do_lock(loader);
+                api_lock_unlock(message.api_lock);
+                break;
+            case LoaderMessageTypeUnlock:
+                loader_do_unlock(loader);
+            }
         }
     }
 
-    furi_record_destroy(RECORD_LOADER);
-    loader_free(loader_instance);
-
     return 0;
-}
-
-FuriPubSub* loader_get_pubsub(Loader* instance) {
-    return instance->pubsub;
-}
+}

+ 4 - 9
applications/services/loader/loader.h

@@ -1,7 +1,5 @@
 #pragma once
-
-#include <core/pubsub.h>
-#include <stdbool.h>
+#include <furi.h>
 
 #ifdef __cplusplus
 extern "C" {
@@ -43,17 +41,14 @@ bool loader_lock(Loader* instance);
 void loader_unlock(Loader* instance);
 
 /** Get loader lock status */
-bool loader_is_locked(const Loader* instance);
-
-/** Show primary loader */
-void loader_show_menu();
+bool loader_is_locked(Loader* instance);
 
 /** Show primary loader */
-void loader_update_menu();
+void loader_show_menu(Loader* instance);
 
 /** Show primary loader */
 FuriPubSub* loader_get_pubsub(Loader* instance);
 
 #ifdef __cplusplus
 }
-#endif
+#endif

+ 117 - 0
applications/services/loader/loader_cli.c

@@ -0,0 +1,117 @@
+#include <furi.h>
+#include <cli/cli.h>
+#include <applications.h>
+#include <lib/toolbox/args.h>
+#include "loader.h"
+
+static void loader_cli_print_usage() {
+    printf("Usage:\r\n");
+    printf("loader <cmd> <args>\r\n");
+    printf("Cmd list:\r\n");
+    printf("\tlist\t - List available applications\r\n");
+    printf("\topen <Application Name:string>\t - Open application by name\r\n");
+    printf("\tinfo\t - Show loader state\r\n");
+}
+
+static void loader_cli_list() {
+    printf("Applications:\r\n");
+    for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) {
+        printf("\t%s\r\n", FLIPPER_APPS[i].name);
+    }
+    printf("Settings:\r\n");
+    for(size_t i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) {
+        printf("\t%s\r\n", FLIPPER_SETTINGS_APPS[i].name);
+    }
+}
+
+static void loader_cli_info(Loader* loader) {
+    if(!loader_is_locked(loader)) {
+        printf("No application is running\r\n");
+    } else {
+        // TODO: print application name ???
+        printf("Application is running\r\n");
+    }
+}
+
+static void loader_cli_open(FuriString* args, Loader* loader) {
+    FuriString* app_name = furi_string_alloc();
+
+    do {
+        if(!args_read_probably_quoted_string_and_trim(args, app_name)) {
+            printf("No application provided\r\n");
+            break;
+        }
+        furi_string_trim(args);
+
+        const char* args_str = furi_string_get_cstr(args);
+        if(strlen(args_str) == 0) {
+            args_str = NULL;
+        }
+
+        const char* app_name_str = furi_string_get_cstr(app_name);
+
+        LoaderStatus status = loader_start(loader, app_name_str, args_str);
+
+        switch(status) {
+        case LoaderStatusOk:
+            break;
+        case LoaderStatusErrorAppStarted:
+            printf("Can't start, application is running");
+            break;
+        case LoaderStatusErrorUnknownApp:
+            printf("%s doesn't exists\r\n", app_name_str);
+            break;
+        case LoaderStatusErrorInternal:
+            printf("Internal error\r\n");
+            break;
+        }
+    } while(false);
+
+    furi_string_free(app_name);
+}
+
+static void loader_cli(Cli* cli, FuriString* args, void* context) {
+    UNUSED(cli);
+    UNUSED(context);
+    Loader* loader = furi_record_open(RECORD_LOADER);
+
+    FuriString* cmd;
+    cmd = furi_string_alloc();
+
+    do {
+        if(!args_read_string_and_trim(args, cmd)) {
+            loader_cli_print_usage();
+            break;
+        }
+
+        if(furi_string_cmp_str(cmd, "list") == 0) {
+            loader_cli_list();
+            break;
+        }
+
+        if(furi_string_cmp_str(cmd, "open") == 0) {
+            loader_cli_open(args, loader);
+            break;
+        }
+
+        if(furi_string_cmp_str(cmd, "info") == 0) {
+            loader_cli_info(loader);
+            break;
+        }
+
+        loader_cli_print_usage();
+    } while(false);
+
+    furi_string_free(cmd);
+    furi_record_close(RECORD_LOADER);
+}
+
+void loader_on_system_start() {
+#ifdef SRV_CLI
+    Cli* cli = furi_record_open(RECORD_CLI);
+    cli_add_command(cli, RECORD_LOADER, CliCommandFlagParallelSafe, loader_cli, NULL);
+    furi_record_close(RECORD_CLI);
+#else
+    UNUSED(loader_cli);
+#endif
+}

+ 48 - 31
applications/services/loader/loader_i.h

@@ -1,39 +1,56 @@
-#include "loader.h"
-
+#pragma once
 #include <furi.h>
-#include <furi_hal.h>
-#include <core/pubsub.h>
-#include <cli/cli.h>
-#include <lib/toolbox/args.h>
-
-#include <gui/view_dispatcher.h>
-
-#include <gui/modules/menu.h>
-#include <gui/modules/submenu.h>
+#include <toolbox/api_lock.h>
+#include "loader.h"
+#include "loader_menu.h"
 
-#include <applications.h>
-#include <assets_icons.h>
+typedef struct {
+    char* args;
+    char* name;
+    FuriThread* thread;
+    bool insomniac;
+} LoaderAppData;
 
 struct Loader {
-    FuriThreadId loader_thread;
-
-    const FlipperApplication* application;
-    FuriThread* application_thread;
-    char* application_arguments;
-
-    Cli* cli;
-    Gui* gui;
-
-    ViewDispatcher* view_dispatcher;
-    Menu* primary_menu;
-    Submenu* settings_menu;
-
-    volatile uint8_t lock_count;
-
     FuriPubSub* pubsub;
+    FuriMessageQueue* queue;
+    LoaderMenu* loader_menu;
+    LoaderAppData app;
 };
 
 typedef enum {
-    LoaderMenuViewPrimary,
-    LoaderMenuViewSettings,
-} LoaderMenuView;
+    LoaderMessageTypeStartByName,
+    LoaderMessageTypeAppClosed,
+    LoaderMessageTypeShowMenu,
+    LoaderMessageTypeMenuClosed,
+    LoaderMessageTypeLock,
+    LoaderMessageTypeUnlock,
+    LoaderMessageTypeIsLocked,
+} LoaderMessageType;
+
+typedef struct {
+    const char* name;
+    const char* args;
+} LoaderMessageStartByName;
+
+typedef struct {
+    LoaderStatus value;
+} LoaderMessageLoaderStatusResult;
+
+typedef struct {
+    bool value;
+} LoaderMessageBoolResult;
+
+typedef struct {
+    FuriApiLock api_lock;
+    LoaderMessageType type;
+
+    union {
+        LoaderMessageStartByName start;
+    };
+
+    union {
+        LoaderMessageLoaderStatusResult* status_value;
+        LoaderMessageBoolResult* bool_value;
+    };
+} LoaderMessage;

+ 187 - 0
applications/services/loader/loader_menu.c

@@ -0,0 +1,187 @@
+#include <gui/gui.h>
+#include <gui/view_dispatcher.h>
+#include <gui/modules/menu.h>
+#include <gui/modules/submenu.h>
+#include <assets_icons.h>
+#include <applications.h>
+
+#include "loader_menu.h"
+
+#define TAG "LoaderMenu"
+
+struct LoaderMenu {
+    Gui* gui;
+    ViewDispatcher* view_dispatcher;
+    Menu* primary_menu;
+    Submenu* settings_menu;
+
+    void (*closed_callback)(void*);
+    void* closed_callback_context;
+
+    void (*click_callback)(const char*, void*);
+    void* click_callback_context;
+
+    FuriThread* thread;
+};
+
+typedef enum {
+    LoaderMenuViewPrimary,
+    LoaderMenuViewSettings,
+} LoaderMenuView;
+
+static int32_t loader_menu_thread(void* p);
+
+LoaderMenu* loader_menu_alloc() {
+    LoaderMenu* loader_menu = malloc(sizeof(LoaderMenu));
+    loader_menu->gui = furi_record_open(RECORD_GUI);
+    loader_menu->view_dispatcher = view_dispatcher_alloc();
+    loader_menu->primary_menu = menu_alloc();
+    loader_menu->settings_menu = submenu_alloc();
+    loader_menu->thread = NULL;
+    return loader_menu;
+}
+
+void loader_menu_free(LoaderMenu* loader_menu) {
+    furi_assert(loader_menu);
+    // check if thread is running
+    furi_assert(!loader_menu->thread);
+
+    submenu_free(loader_menu->settings_menu);
+    menu_free(loader_menu->primary_menu);
+    view_dispatcher_free(loader_menu->view_dispatcher);
+    furi_record_close(RECORD_GUI);
+    free(loader_menu);
+}
+
+void loader_menu_start(LoaderMenu* loader_menu) {
+    furi_assert(loader_menu);
+    furi_assert(!loader_menu->thread);
+    loader_menu->thread = furi_thread_alloc_ex(TAG, 1024, loader_menu_thread, loader_menu);
+    furi_thread_start(loader_menu->thread);
+}
+
+void loader_menu_stop(LoaderMenu* loader_menu) {
+    furi_assert(loader_menu);
+    furi_assert(loader_menu->thread);
+    view_dispatcher_stop(loader_menu->view_dispatcher);
+    furi_thread_join(loader_menu->thread);
+    furi_thread_free(loader_menu->thread);
+    loader_menu->thread = NULL;
+}
+
+void loader_menu_set_closed_callback(
+    LoaderMenu* loader_menu,
+    void (*callback)(void*),
+    void* context) {
+    loader_menu->closed_callback = callback;
+    loader_menu->closed_callback_context = context;
+}
+
+void loader_menu_set_click_callback(
+    LoaderMenu* loader_menu,
+    void (*callback)(const char*, void*),
+    void* context) {
+    loader_menu->click_callback = callback;
+    loader_menu->click_callback_context = context;
+}
+
+static void loader_menu_callback(void* context, uint32_t index) {
+    LoaderMenu* loader_menu = context;
+    const char* name = FLIPPER_APPS[index].name;
+    if(loader_menu->click_callback) {
+        loader_menu->click_callback(name, loader_menu->click_callback_context);
+    }
+}
+
+static void loader_menu_settings_menu_callback(void* context, uint32_t index) {
+    LoaderMenu* loader_menu = context;
+    const char* name = FLIPPER_SETTINGS_APPS[index].name;
+    if(loader_menu->click_callback) {
+        loader_menu->click_callback(name, loader_menu->click_callback_context);
+    }
+}
+
+static void loader_menu_switch_to_settings(void* context, uint32_t index) {
+    UNUSED(index);
+    LoaderMenu* loader_menu = context;
+    view_dispatcher_switch_to_view(loader_menu->view_dispatcher, LoaderMenuViewSettings);
+}
+
+static uint32_t loader_menu_switch_to_primary(void* context) {
+    UNUSED(context);
+    return LoaderMenuViewPrimary;
+}
+
+static uint32_t loader_menu_exit(void* context) {
+    UNUSED(context);
+    return VIEW_NONE;
+}
+
+static void loader_menu_build_menu(LoaderMenu* loader_menu) {
+    size_t i;
+    for(i = 0; i < FLIPPER_APPS_COUNT; i++) {
+        menu_add_item(
+            loader_menu->primary_menu,
+            FLIPPER_APPS[i].name,
+            FLIPPER_APPS[i].icon,
+            i,
+            loader_menu_callback,
+            (void*)loader_menu);
+    }
+    menu_add_item(
+        loader_menu->primary_menu,
+        "Settings",
+        &A_Settings_14,
+        i++,
+        loader_menu_switch_to_settings,
+        loader_menu);
+};
+
+static void loader_menu_build_submenu(LoaderMenu* loader_menu) {
+    for(size_t i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) {
+        submenu_add_item(
+            loader_menu->settings_menu,
+            FLIPPER_SETTINGS_APPS[i].name,
+            i,
+            loader_menu_settings_menu_callback,
+            loader_menu);
+    }
+}
+
+static int32_t loader_menu_thread(void* p) {
+    LoaderMenu* loader_menu = p;
+    furi_assert(loader_menu);
+
+    loader_menu_build_menu(loader_menu);
+    loader_menu_build_submenu(loader_menu);
+
+    view_dispatcher_attach_to_gui(
+        loader_menu->view_dispatcher, loader_menu->gui, ViewDispatcherTypeFullscreen);
+
+    // Primary menu
+    View* primary_view = menu_get_view(loader_menu->primary_menu);
+    view_set_context(primary_view, loader_menu->primary_menu);
+    view_set_previous_callback(primary_view, loader_menu_exit);
+    view_dispatcher_add_view(loader_menu->view_dispatcher, LoaderMenuViewPrimary, primary_view);
+
+    // Settings menu
+    View* settings_view = submenu_get_view(loader_menu->settings_menu);
+    view_set_context(settings_view, loader_menu->settings_menu);
+    view_set_previous_callback(settings_view, loader_menu_switch_to_primary);
+    view_dispatcher_add_view(loader_menu->view_dispatcher, LoaderMenuViewSettings, settings_view);
+
+    view_dispatcher_enable_queue(loader_menu->view_dispatcher);
+    view_dispatcher_switch_to_view(loader_menu->view_dispatcher, LoaderMenuViewPrimary);
+
+    // run view dispatcher
+    view_dispatcher_run(loader_menu->view_dispatcher);
+
+    view_dispatcher_remove_view(loader_menu->view_dispatcher, LoaderMenuViewPrimary);
+    view_dispatcher_remove_view(loader_menu->view_dispatcher, LoaderMenuViewSettings);
+
+    if(loader_menu->closed_callback) {
+        loader_menu->closed_callback(loader_menu->closed_callback_context);
+    }
+
+    return 0;
+}

+ 30 - 0
applications/services/loader/loader_menu.h

@@ -0,0 +1,30 @@
+#pragma once
+#include <furi.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct LoaderMenu LoaderMenu;
+
+LoaderMenu* loader_menu_alloc();
+
+void loader_menu_free(LoaderMenu* loader_menu);
+
+void loader_menu_start(LoaderMenu* loader_menu);
+
+void loader_menu_stop(LoaderMenu* loader_menu);
+
+void loader_menu_set_closed_callback(
+    LoaderMenu* loader_menu,
+    void (*callback)(void*),
+    void* context);
+
+void loader_menu_set_click_callback(
+    LoaderMenu* loader_menu,
+    void (*callback)(const char*, void*),
+    void* context);
+
+#ifdef __cplusplus
+}
+#endif

+ 30 - 22
applications/services/rpc/rpc.c

@@ -57,6 +57,10 @@ static RpcSystemCallbacks rpc_systems[] = {
         .alloc = rpc_system_property_alloc,
         .free = NULL,
     },
+    {
+        .alloc = rpc_desktop_alloc,
+        .free = rpc_desktop_free,
+    },
 };
 
 struct RpcSession {
@@ -326,31 +330,35 @@ static int32_t rpc_session_worker(void* context) {
     return 0;
 }
 
-static void rpc_session_free_callback(FuriThreadState thread_state, void* context) {
-    furi_assert(context);
-
+static void rpc_session_thread_pending_callback(void* context, uint32_t arg) {
+    UNUSED(arg);
     RpcSession* session = (RpcSession*)context;
 
-    if(thread_state == FuriThreadStateStopped) {
-        for(size_t i = 0; i < COUNT_OF(rpc_systems); ++i) {
-            if(rpc_systems[i].free) {
-                rpc_systems[i].free(session->system_contexts[i]);
-            }
-        }
-        free(session->system_contexts);
-        free(session->decoded_message);
-        RpcHandlerDict_clear(session->handlers);
-        furi_stream_buffer_free(session->stream);
-
-        furi_mutex_acquire(session->callbacks_mutex, FuriWaitForever);
-        if(session->terminated_callback) {
-            session->terminated_callback(session->context);
+    for(size_t i = 0; i < COUNT_OF(rpc_systems); ++i) {
+        if(rpc_systems[i].free) {
+            (rpc_systems[i].free)(session->system_contexts[i]);
         }
-        furi_mutex_release(session->callbacks_mutex);
+    }
+    free(session->system_contexts);
+    free(session->decoded_message);
+    RpcHandlerDict_clear(session->handlers);
+    furi_stream_buffer_free(session->stream);
+
+    furi_mutex_acquire(session->callbacks_mutex, FuriWaitForever);
+    if(session->terminated_callback) {
+        session->terminated_callback(session->context);
+    }
+    furi_mutex_release(session->callbacks_mutex);
+
+    furi_mutex_free(session->callbacks_mutex);
+    furi_thread_join(session->thread);
+    furi_thread_free(session->thread);
+    free(session);
+}
 
-        furi_mutex_free(session->callbacks_mutex);
-        furi_thread_free(session->thread);
-        free(session);
+static void rpc_session_thread_state_callback(FuriThreadState thread_state, void* context) {
+    if(thread_state == FuriThreadStateStopped) {
+        furi_timer_pending_callback(rpc_session_thread_pending_callback, context, 0);
     }
 }
 
@@ -385,7 +393,7 @@ RpcSession* rpc_session_open(Rpc* rpc, RpcOwner owner) {
     session->thread = furi_thread_alloc_ex("RpcSessionWorker", 3072, rpc_session_worker, session);
 
     furi_thread_set_state_context(session->thread, session);
-    furi_thread_set_state_callback(session->thread, rpc_session_free_callback);
+    furi_thread_set_state_callback(session->thread, rpc_session_thread_state_callback);
 
     furi_thread_start(session->thread);
 

+ 73 - 0
applications/services/rpc/rpc_desktop.c

@@ -0,0 +1,73 @@
+#include "flipper.pb.h"
+#include "rpc_i.h"
+#include <desktop/desktop.h>
+#include "desktop.pb.h"
+
+#define TAG "RpcDesktop"
+
+typedef struct {
+    RpcSession* session;
+    Desktop* desktop;
+} RpcDesktop;
+
+static void rpc_desktop_on_is_locked_request(const PB_Main* request, void* context) {
+    furi_assert(request);
+    furi_assert(context);
+    furi_assert(request->which_content == PB_Main_desktop_is_locked_request_tag);
+
+    FURI_LOG_D(TAG, "IsLockedRequest");
+    RpcDesktop* rpc_desktop = context;
+    RpcSession* session = rpc_desktop->session;
+
+    PB_CommandStatus ret = desktop_api_is_locked(rpc_desktop->desktop) ? PB_CommandStatus_OK :
+                                                                         PB_CommandStatus_ERROR;
+
+    rpc_send_and_release_empty(session, request->command_id, ret);
+}
+
+static void rpc_desktop_on_unlock_request(const PB_Main* request, void* context) {
+    furi_assert(request);
+    furi_assert(context);
+    furi_assert(request->which_content == PB_Main_desktop_unlock_request_tag);
+
+    FURI_LOG_D(TAG, "UnlockRequest");
+    RpcDesktop* rpc_desktop = context;
+    RpcSession* session = rpc_desktop->session;
+
+    desktop_api_unlock(rpc_desktop->desktop);
+
+    rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK);
+}
+
+void* rpc_desktop_alloc(RpcSession* session) {
+    furi_assert(session);
+
+    RpcDesktop* rpc_desktop = malloc(sizeof(RpcDesktop));
+    rpc_desktop->desktop = furi_record_open(RECORD_DESKTOP);
+    rpc_desktop->session = session;
+
+    RpcHandler rpc_handler = {
+        .message_handler = NULL,
+        .decode_submessage = NULL,
+        .context = rpc_desktop,
+    };
+
+    rpc_handler.message_handler = rpc_desktop_on_is_locked_request;
+    rpc_add_handler(session, PB_Main_desktop_is_locked_request_tag, &rpc_handler);
+
+    rpc_handler.message_handler = rpc_desktop_on_unlock_request;
+    rpc_add_handler(session, PB_Main_desktop_unlock_request_tag, &rpc_handler);
+
+    return rpc_desktop;
+}
+
+void rpc_desktop_free(void* context) {
+    furi_assert(context);
+    RpcDesktop* rpc_desktop = context;
+
+    furi_assert(rpc_desktop->desktop);
+    furi_record_close(RECORD_DESKTOP);
+
+    rpc_desktop->session = NULL;
+    free(rpc_desktop);
+}

+ 3 - 0
applications/services/rpc/rpc_i.h

@@ -36,6 +36,9 @@ void* rpc_system_gpio_alloc(RpcSession* session);
 void rpc_system_gpio_free(void* ctx);
 void* rpc_system_property_alloc(RpcSession* session);
 
+void* rpc_desktop_alloc(RpcSession* session);
+void rpc_desktop_free(void* ctx);
+
 void rpc_debug_print_message(const PB_Main* message);
 void rpc_debug_print_data(const char* prefix, uint8_t* buffer, size_t size);
 

+ 1 - 0
applications/services/storage/storage_external_api.c

@@ -803,6 +803,7 @@ void storage_file_free(File* file) {
 }
 
 FuriPubSub* storage_get_pubsub(Storage* storage) {
+    furi_assert(storage);
     return storage->pubsub;
 }
 

+ 1 - 0
applications/services/storage/storages/storage_ext.c

@@ -337,6 +337,7 @@ static bool storage_ext_file_close(void* ctx, File* file) {
     file->internal_error_id = f_close(file_data);
     file->error_id = storage_ext_parse_error(file->internal_error_id);
     free(file_data);
+    storage_set_storage_file_data(file, NULL, storage);
     return (file->error_id == FSE_OK);
 }
 

+ 6 - 2
applications/settings/about/about.c

@@ -7,6 +7,7 @@
 #include <furi_hal_version.h>
 #include <furi_hal_region.h>
 #include <furi_hal_bt.h>
+#include <furi_hal_info.h>
 
 typedef DialogMessageButton (*AboutDialogScreen)(DialogsApp* dialogs, DialogMessage* message);
 
@@ -134,14 +135,17 @@ static DialogMessageButton fw_version_screen(DialogsApp* dialogs, DialogMessage*
     if(!ver) { //-V1051
         furi_string_cat_printf(buffer, "No info\n");
     } else {
+        uint16_t api_major, api_minor;
+        furi_hal_info_get_api_version(&api_major, &api_minor);
         furi_string_cat_printf(
             buffer,
-            "%s [%s]\n%s%s [%s] %s\n[%d] %s",
+            "%s [%s]\n%s%s [%d.%d] %s\n[%d] %s",
             version_get_version(ver),
             version_get_builddate(ver),
             version_get_dirty_flag(ver) ? "[!] " : "",
             version_get_githash(ver),
-            version_get_gitbranchnum(ver),
+            api_major,
+            api_minor,
             c2_ver ? c2_ver->StackTypeString : "<none>",
             version_get_target(ver),
             version_get_gitbranch(ver));

+ 2 - 2
applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c

@@ -1,7 +1,7 @@
 #include <stdint.h>
 #include <core/check.h>
 #include <gui/scene_manager.h>
-#include <desktop/helpers/pin_lock.h>
+#include <desktop/helpers/pin.h>
 #include "../desktop_settings_app.h"
 #include <desktop/desktop_settings.h>
 #include <desktop/views/desktop_view_pin_input.h>
@@ -18,7 +18,7 @@ static void pin_auth_done_callback(const PinCode* pin_code, void* context) {
     DesktopSettingsApp* app = context;
 
     app->pincode_buffer = *pin_code;
-    if(desktop_pins_are_equal(&app->settings.pin_code, pin_code)) {
+    if(desktop_pin_compare(&app->settings.pin_code, pin_code)) {
         view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_EQUAL);
     } else {
         view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_DIFFERENT);

+ 1 - 1
applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c

@@ -6,7 +6,7 @@
 #include <desktop/views/desktop_view_pin_input.h>
 #include "desktop_settings_scene.h"
 #include "desktop_settings_scene_i.h"
-#include <desktop/helpers/pin_lock.h>
+#include <desktop/helpers/pin.h>
 #include "../desktop_settings_app.h"
 
 #define SCENE_EVENT_EXIT (0U)

+ 2 - 2
applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c

@@ -7,7 +7,7 @@
 #include <desktop/views/desktop_view_pin_input.h>
 #include "desktop_settings_scene.h"
 #include "desktop_settings_scene_i.h"
-#include <desktop/helpers/pin_lock.h>
+#include <desktop/helpers/pin.h>
 
 #define SCENE_EVENT_EXIT (0U)
 #define SCENE_EVENT_1ST_PIN_ENTERED (1U)
@@ -25,7 +25,7 @@ static void pin_setup_done_callback(const PinCode* pin_code, void* context) {
         view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_1ST_PIN_ENTERED);
     } else {
         app->pincode_buffer_filled = false;
-        if(desktop_pins_are_equal(&app->pincode_buffer, pin_code)) {
+        if(desktop_pin_compare(&app->pincode_buffer, pin_code)) {
             view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_EQUAL);
         } else {
             view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_DIFFERENT);

+ 3 - 1
applications/settings/power_settings_app/views/battery_info.c

@@ -53,7 +53,9 @@ static void draw_battery(Canvas* canvas, BatteryInfoModel* data, int x, int y) {
             (uint32_t)(data->vbus_voltage),
             (uint32_t)(data->vbus_voltage * 10) % 10,
             current);
-    } else if(current < 0) {
+    } else if(current < -5) {
+        // Often gauge reports anything in the range 1~5ma as 5ma
+        // That brings confusion, so we'll treat it as Napping
         snprintf(
             emote,
             sizeof(emote),

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác