Просмотр исходного кода

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

0xchocolate 2 лет назад
Родитель
Сommit
7241f9801e
100 измененных файлов с 4672 добавлено и 1907 удалено
  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),

Некоторые файлы не были показаны из-за большого количества измененных файлов