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

Merge remote-tracking branch 'origin/dev' into feature_wifi_marauder_app

0xchocolate 3 лет назад
Родитель
Сommit
f29be380cb
100 измененных файлов с 1117 добавлено и 222 удалено
  1. 1 1
      .github/workflows/build.yml
  2. 0 1
      .github/workflows/pvs_studio.yml
  3. 52 0
      .github/workflows/unit_tests.yml
  4. 0 3
      .gitmodules
  5. 1 1
      .vscode/example/c_cpp_properties.json
  6. 19 0
      .vscode/example/launch.json
  7. 3 3
      .vscode/example/settings.json
  8. 2 2
      .vscode/example/tasks.json
  9. 12 4
      SConstruct
  10. 1 0
      applications/debug/file_browser_test/application.fam
  11. 1 1
      applications/debug/file_browser_test/file_browser_app.c
  12. BIN
      applications/debug/file_browser_test/icons/badusb_10px.png
  13. 24 0
      applications/examples/example_images/ReadMe.md
  14. 1 0
      applications/main/bad_usb/bad_usb_app_i.h
  15. 1 0
      applications/main/bad_usb/views/bad_usb_view.c
  16. 1 0
      applications/main/fap_loader/fap_loader_app.c
  17. 1 0
      applications/main/gpio/gpio_app_i.h
  18. 3 0
      applications/main/ibutton/ibutton.c
  19. 1 0
      applications/main/ibutton/ibutton_i.h
  20. 0 3
      applications/main/ibutton/scenes/ibutton_scene_add_value.c
  21. 0 3
      applications/main/ibutton/scenes/ibutton_scene_emulate.c
  22. 1 2
      applications/main/ibutton/scenes/ibutton_scene_read.c
  23. 2 0
      applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c
  24. 10 0
      applications/main/ibutton/scenes/ibutton_scene_save_name.c
  25. 0 2
      applications/main/ibutton/scenes/ibutton_scene_save_success.c
  26. 2 0
      applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c
  27. 2 0
      applications/main/ibutton/scenes/ibutton_scene_start.c
  28. 77 116
      applications/main/infrared/infrared_cli.c
  29. 1 0
      applications/main/infrared/infrared_i.h
  30. 1 0
      applications/main/infrared/scenes/infrared_scene_config.h
  31. 2 0
      applications/main/infrared/scenes/infrared_scene_learn.c
  32. 0 3
      applications/main/infrared/scenes/infrared_scene_learn_done.c
  33. 2 0
      applications/main/infrared/scenes/infrared_scene_learn_enter_name.c
  34. 0 3
      applications/main/infrared/scenes/infrared_scene_learn_success.c
  35. 7 1
      applications/main/infrared/scenes/infrared_scene_universal.c
  36. 133 0
      applications/main/infrared/scenes/infrared_scene_universal_audio.c
  37. 4 1
      applications/main/lfrfid/lfrfid.c
  38. 1 0
      applications/main/lfrfid/lfrfid_i.h
  39. 0 3
      applications/main/lfrfid/scenes/lfrfid_scene_emulate.c
  40. 3 0
      applications/main/lfrfid/scenes/lfrfid_scene_extra_actions.c
  41. 1 2
      applications/main/lfrfid/scenes/lfrfid_scene_read.c
  42. 2 0
      applications/main/lfrfid/scenes/lfrfid_scene_read_key_menu.c
  43. 0 2
      applications/main/lfrfid/scenes/lfrfid_scene_save_data.c
  44. 8 0
      applications/main/lfrfid/scenes/lfrfid_scene_save_name.c
  45. 0 2
      applications/main/lfrfid/scenes/lfrfid_scene_save_success.c
  46. 2 0
      applications/main/lfrfid/scenes/lfrfid_scene_saved_key_menu.c
  47. 2 0
      applications/main/lfrfid/scenes/lfrfid_scene_start.c
  48. 1 0
      applications/main/lfrfid/views/lfrfid_view_read.c
  49. 4 0
      applications/main/nfc/nfc.c
  50. 1 0
      applications/main/nfc/nfc_i.h
  51. 6 0
      applications/main/nfc/scenes/nfc_scene_config.h
  52. 5 2
      applications/main/nfc/scenes/nfc_scene_detect_reader.c
  53. 0 2
      applications/main/nfc/scenes/nfc_scene_emulate_uid.c
  54. 0 2
      applications/main/nfc/scenes/nfc_scene_emv_read_success.c
  55. 4 0
      applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c
  56. 0 2
      applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c
  57. 2 0
      applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c
  58. 5 2
      applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c
  59. 0 3
      applications/main/nfc/scenes/nfc_scene_mf_classic_read_success.c
  60. 98 0
      applications/main/nfc/scenes/nfc_scene_mf_classic_update.c
  61. 44 0
      applications/main/nfc/scenes/nfc_scene_mf_classic_update_success.c
  62. 92 0
      applications/main/nfc/scenes/nfc_scene_mf_classic_write.c
  63. 58 0
      applications/main/nfc/scenes/nfc_scene_mf_classic_write_fail.c
  64. 44 0
      applications/main/nfc/scenes/nfc_scene_mf_classic_write_success.c
  65. 53 0
      applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c
  66. 6 0
      applications/main/nfc/scenes/nfc_scene_mf_desfire_menu.c
  67. 0 2
      applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c
  68. 6 0
      applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c
  69. 0 2
      applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c
  70. 0 4
      applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c
  71. 0 2
      applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c
  72. 2 0
      applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c
  73. 6 0
      applications/main/nfc/scenes/nfc_scene_nfca_menu.c
  74. 0 3
      applications/main/nfc/scenes/nfc_scene_nfca_read_success.c
  75. 6 1
      applications/main/nfc/scenes/nfc_scene_read.c
  76. 0 2
      applications/main/nfc/scenes/nfc_scene_read_card_success.c
  77. 8 0
      applications/main/nfc/scenes/nfc_scene_save_name.c
  78. 0 2
      applications/main/nfc/scenes/nfc_scene_save_success.c
  79. 36 0
      applications/main/nfc/scenes/nfc_scene_saved_menu.c
  80. 0 2
      applications/main/nfc/scenes/nfc_scene_set_uid.c
  81. 4 0
      applications/main/nfc/scenes/nfc_scene_start.c
  82. 37 1
      applications/main/nfc/views/detect_reader.c
  83. 2 0
      applications/main/nfc/views/detect_reader.h
  84. 0 2
      applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c
  85. 50 7
      applications/main/subghz/scenes/subghz_scene_read_raw.c
  86. 2 0
      applications/main/subghz/scenes/subghz_scene_receiver.c
  87. 52 0
      applications/main/subghz/scenes/subghz_scene_receiver_config.c
  88. 0 2
      applications/main/subghz/scenes/subghz_scene_receiver_info.c
  89. 12 0
      applications/main/subghz/scenes/subghz_scene_save_name.c
  90. 0 3
      applications/main/subghz/scenes/subghz_scene_save_success.c
  91. 0 2
      applications/main/subghz/scenes/subghz_scene_set_type.c
  92. 2 0
      applications/main/subghz/scenes/subghz_scene_start.c
  93. 1 1
      applications/main/subghz/scenes/subghz_scene_transmitter.c
  94. 1 0
      applications/main/subghz/subghz.c
  95. 1 1
      applications/main/subghz/subghz_i.c
  96. 4 0
      applications/main/subghz/subghz_i.h
  97. 68 9
      applications/main/subghz/views/subghz_read_raw.c
  98. 5 2
      applications/main/subghz/views/subghz_read_raw.h
  99. 1 0
      applications/main/u2f/u2f_app_i.h
  100. 1 0
      applications/main/u2f/views/u2f_view.c

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

@@ -127,7 +127,7 @@ jobs:
             **Compiled firmware for commit `${{steps.names.outputs.commit_sha}}`:**
             - [📦 Update package](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz)
             - [📥 DFU file](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-full-${{steps.names.outputs.suffix}}.dfu)
-            - [☁️ Web/App updater](https://my.flipp.dev/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.branch_name}}&version=${{steps.names.outputs.commit_sha}})
+            - [☁️ Web/App updater](https://lab.flipper.net/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.branch_name}}&version=${{steps.names.outputs.commit_sha}})
           edit-mode: replace
 
   compact:

+ 0 - 1
.github/workflows/pvs_studio.yml

@@ -65,7 +65,6 @@ jobs:
           pvs-studio-analyzer credentials ${{ secrets.PVS_STUDIO_CREDENTIALS }}
           pvs-studio-analyzer analyze \
               @.pvsoptions \
-              --disableLicenseExpirationCheck \
               -j$(grep -c processor /proc/cpuinfo) \
               -f build/f7-firmware-DC/compile_commands.json \
               -o PVS-Studio.log

+ 52 - 0
.github/workflows/unit_tests.yml

@@ -0,0 +1,52 @@
+name: 'Unit tests'
+
+on:
+  pull_request:
+
+env:
+  TARGETS: f7
+  DEFAULT_TARGET: f7
+
+jobs:
+  main:
+    runs-on: [self-hosted, FlipperZeroTest]
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v2
+        with:
+          fetch-depth: 0
+          ref: ${{ github.event.pull_request.head.sha }}
+
+      - name: 'Get flipper from device manager (mock)'
+        id: device
+        run: |
+          echo "flipper=/dev/ttyACM0" >> $GITHUB_OUTPUT
+
+      - name: 'Compile unit tests firmware'
+        id: compile
+        run: |
+          FBT_TOOLCHAIN_PATH=/opt ./fbt flash OPENOCD_ADAPTER_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1
+
+      - name: 'Wait for flipper to finish updating'
+        id: connect
+        if: steps.compile.outcome == 'success'
+        run: |
+          python3 ./scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}}
+
+      - name: 'Format flipper SD card'
+        id: format
+        if: steps.connect.outcome == 'success'
+        run: |
+          ./scripts/storage.py -p ${{steps.device.outputs.flipper}} format_ext
+
+      - name: 'Copy assets and unit tests data to flipper'
+        id: copy
+        if: steps.format.outcome == 'success'
+        run: |
+          ./scripts/storage.py -p ${{steps.device.outputs.flipper}} send assets/resources /ext
+          ./scripts/storage.py -p ${{steps.device.outputs.flipper}} send assets/unit_tests /ext/unit_tests
+
+      - name: 'Run units and validate results'
+        if: steps.copy.outcome == 'success'
+        run: |
+          python3 ./scripts/testing/units.py ${{steps.device.outputs.flipper}}

+ 0 - 3
.gitmodules

@@ -22,9 +22,6 @@
 [submodule "lib/microtar"]
 	path = lib/microtar
 	url = https://github.com/amachronic/microtar.git
-[submodule "lib/scons"]
-	path = lib/scons
-	url = https://github.com/SCons/scons.git
 [submodule "lib/mbedtls"]
 	path = lib/mbedtls
 	url = https://github.com/Mbed-TLS/mbedtls.git

+ 1 - 1
.vscode/example/c_cpp_properties.json

@@ -2,7 +2,7 @@
     "configurations": [
         {
             "name": "Win32",
-            "compilerPath": "${workspaceFolder}/toolchain/i686-windows/bin/arm-none-eabi-gcc.exe",
+            "compilerPath": "${workspaceFolder}/toolchain/x86_64-windows/bin/arm-none-eabi-gcc.exe",
             "intelliSenseMode": "gcc-arm",
             "compileCommands": "${workspaceFolder}/build/latest/compile_commands.json",
             "configurationProvider": "ms-vscode.cpptools",

+ 19 - 0
.vscode/example/launch.json

@@ -79,6 +79,25 @@
             ]
             // "showDevDebugOutput": "raw",
         },
+        {
+            "name": "Attach FW (DAP)",
+            "cwd": "${workspaceFolder}",
+            "executable": "./build/latest/firmware.elf",
+            "request": "attach",
+            "type": "cortex-debug",
+            "servertype": "openocd",
+            "device": "cmsis-dap",
+            "svdFile": "./debug/STM32WB55_CM4.svd",
+            "rtos": "FreeRTOS",
+            "configFiles": [
+                "interface/cmsis-dap.cfg",
+                "./debug/stm32wbx.cfg",
+            ],
+            "postAttachCommands": [
+                "source debug/flipperapps.py",
+            ],
+            // "showDevDebugOutput": "raw",
+        },
         {
             "name": "fbt debug",
             "type": "python",

+ 3 - 3
.vscode/example/settings.json

@@ -6,13 +6,13 @@
     "cortex-debug.enableTelemetry": false,
     "cortex-debug.variableUseNaturalFormat": true,
     "cortex-debug.showRTOS": true,
-    "cortex-debug.armToolchainPath.windows": "${workspaceFolder}/toolchain/i686-windows/bin",
+    "cortex-debug.armToolchainPath.windows": "${workspaceFolder}/toolchain/x86_64-windows/bin",
     "cortex-debug.armToolchainPath.linux": "${workspaceFolder}/toolchain/x86_64-linux/bin",
     "cortex-debug.armToolchainPath.osx": "${workspaceFolder}/toolchain/x86_64-darwin/bin",
-    "cortex-debug.openocdPath.windows": "${workspaceFolder}/toolchain/i686-windows/openocd/bin/openocd.exe",
+    "cortex-debug.openocdPath.windows": "${workspaceFolder}/toolchain/x86_64-windows/openocd/bin/openocd.exe",
     "cortex-debug.openocdPath.linux": "${workspaceFolder}/toolchain/x86_64-linux/openocd/bin/openocd",
     "cortex-debug.openocdPath.osx": "${workspaceFolder}/toolchain/x86_64-darwin/openocd/bin/openocd",
-    "cortex-debug.gdbPath.windows": "${workspaceFolder}/toolchain/i686-windows/bin/arm-none-eabi-gdb-py.bat",
+    "cortex-debug.gdbPath.windows": "${workspaceFolder}/toolchain/x86_64-windows/bin/arm-none-eabi-gdb-py.bat",
     "cortex-debug.gdbPath.linux": "${workspaceFolder}/toolchain/x86_64-linux/bin/arm-none-eabi-gdb-py",
     "cortex-debug.gdbPath.osx": "${workspaceFolder}/toolchain/x86_64-darwin/bin/arm-none-eabi-gdb-py",
     "editor.formatOnSave": true,

+ 2 - 2
.vscode/example/tasks.json

@@ -109,13 +109,13 @@
             "label": "[Debug] Build FAPs",
             "group": "build",
             "type": "shell",
-            "command": "./fbt plugin_dist"
+            "command": "./fbt fap_dist"
         },
         {
             "label": "[Release] Build FAPs",
             "group": "build",
             "type": "shell",
-            "command": "./fbt COMPACT=1 DEBUG=0 plugin_dist"
+            "command": "./fbt COMPACT=1 DEBUG=0 fap_dist"
         },
         {
             "label": "[Debug] Launch App on Flipper",

+ 12 - 4
SConstruct

@@ -156,11 +156,9 @@ Depends(fap_dist, firmware_env["FW_EXTAPPS"]["validators"].values())
 Alias("fap_dist", fap_dist)
 # distenv.Default(fap_dist)
 
-plugin_resources_dist = list(
-    distenv.Install(f"#/assets/resources/apps/{dist_entry[0]}", dist_entry[1])
-    for dist_entry in firmware_env["FW_EXTAPPS"]["dist"].values()
+distenv.Depends(
+    firmware_env["FW_RESOURCES"], firmware_env["FW_EXTAPPS"]["resources_dist"]
 )
-distenv.Depends(firmware_env["FW_RESOURCES"], plugin_resources_dist)
 
 
 # Target for bundling core2 package for qFlipper
@@ -291,6 +289,16 @@ distenv.PhonyTarget(
     "@echo $( ${BLACKMAGIC_ADDR} $)",
 )
 
+
+# Find STLink probe ids
+distenv.PhonyTarget(
+    "get_stlink",
+    distenv.Action(
+        lambda **kw: distenv.GetDevices(),
+        None,
+    ),
+)
+
 # Prepare vscode environment
 vscode_dist = distenv.Install("#.vscode", distenv.Glob("#.vscode/example/*"))
 distenv.Precious(vscode_dist)

+ 1 - 0
applications/debug/file_browser_test/application.fam

@@ -8,4 +8,5 @@ App(
     stack_size=2 * 1024,
     order=150,
     fap_category="Debug",
+    fap_icon_assets="icons",
 )

+ 1 - 1
applications/debug/file_browser_test/file_browser_app.c

@@ -1,4 +1,4 @@
-#include "assets_icons.h"
+#include <file_browser_test_icons.h>
 #include "file_browser_app_i.h"
 #include "gui/modules/file_browser.h"
 #include <furi.h>

BIN
applications/debug/file_browser_test/icons/badusb_10px.png


+ 24 - 0
applications/examples/example_images/ReadMe.md

@@ -0,0 +1,24 @@
+# Application icons
+To use icons, do the following:
+* add a line to the application manifest: `fap_icon_assets="folder"`, where `folder` points to the folder where your icons are located
+* add `#include "application_id_icons.h"` to the application code, where `application_id` is the appid from the manifest
+* every icon in the folder will be available as a `I_icon_name` variable, where `icon_name` is the name of the icon file without the extension
+
+## Example
+We have an application with the following manifest:
+```
+App(
+    appid="example_images",
+    ...
+    fap_icon_assets="images",
+)
+```
+
+So the icons are in the `images` folder and will be available in the generated `example_images_icons.h` file.
+
+The example code is located in `example_images_main.c` and contains the following line:
+```
+#include "example_images_icons.h"
+```
+
+Image `dolphin_71x25.png` is available as `I_dolphin_71x25`.

+ 1 - 0
applications/main/bad_usb/bad_usb_app_i.h

@@ -5,6 +5,7 @@
 #include "bad_usb_script.h"
 
 #include <gui/gui.h>
+#include <assets_icons.h>
 #include <gui/view_dispatcher.h>
 #include <gui/scene_manager.h>
 #include <gui/modules/submenu.h>

+ 1 - 0
applications/main/bad_usb/views/bad_usb_view.c

@@ -1,6 +1,7 @@
 #include "bad_usb_view.h"
 #include "../bad_usb_script.h"
 #include <gui/elements.h>
+#include <assets_icons.h>
 
 #define MAX_NAME_LEN 64
 

+ 1 - 0
applications/main/fap_loader/fap_loader_app.c

@@ -1,5 +1,6 @@
 #include <furi.h>
 #include <gui/gui.h>
+#include <assets_icons.h>
 #include <gui/view_dispatcher.h>
 #include <storage/storage.h>
 #include <gui/modules/loading.h>

+ 1 - 0
applications/main/gpio/gpio_app_i.h

@@ -15,6 +15,7 @@
 #include <gui/modules/widget.h>
 #include "views/gpio_test.h"
 #include "views/gpio_usb_uart.h"
+#include <assets_icons.h>
 
 struct GpioApp {
     Gui* gui;

+ 3 - 0
applications/main/ibutton/ibutton.c

@@ -5,6 +5,7 @@
 #include <toolbox/path.h>
 #include <flipper_format/flipper_format.h>
 #include <rpc/rpc_app.h>
+#include <dolphin/dolphin.h>
 
 #define TAG "iButtonApp"
 
@@ -337,11 +338,13 @@ int32_t ibutton_app(void* p) {
         view_dispatcher_attach_to_gui(
             ibutton->view_dispatcher, ibutton->gui, ViewDispatcherTypeDesktop);
         scene_manager_next_scene(ibutton->scene_manager, iButtonSceneRpc);
+        DOLPHIN_DEED(DolphinDeedIbuttonEmulate);
     } else {
         view_dispatcher_attach_to_gui(
             ibutton->view_dispatcher, ibutton->gui, ViewDispatcherTypeFullscreen);
         if(key_loaded) {
             scene_manager_next_scene(ibutton->scene_manager, iButtonSceneEmulate);
+            DOLPHIN_DEED(DolphinDeedIbuttonEmulate);
         } else {
             scene_manager_next_scene(ibutton->scene_manager, iButtonSceneStart);
         }

+ 1 - 0
applications/main/ibutton/ibutton_i.h

@@ -4,6 +4,7 @@
 
 #include <gui/gui.h>
 #include <gui/view.h>
+#include <assets_icons.h>
 #include <gui/view_dispatcher.h>
 #include <gui/scene_manager.h>
 #include <notification/notification_messages.h>

+ 0 - 3
applications/main/ibutton/scenes/ibutton_scene_add_value.c

@@ -1,7 +1,5 @@
 #include "../ibutton_i.h"
 
-#include <dolphin/dolphin.h>
-
 void ibutton_scene_add_type_byte_input_callback(void* context) {
     iButton* ibutton = context;
     view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventByteEditResult);
@@ -38,7 +36,6 @@ bool ibutton_scene_add_value_on_event(void* context, SceneManagerEvent event) {
         consumed = true;
         if(event.event == iButtonCustomEventByteEditResult) {
             ibutton_key_set_data(ibutton->key, new_key_data, IBUTTON_KEY_DATA_SIZE);
-            DOLPHIN_DEED(DolphinDeedIbuttonAdd);
             scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSaveName);
         }
     }

+ 0 - 3
applications/main/ibutton/scenes/ibutton_scene_emulate.c

@@ -1,6 +1,5 @@
 #include "../ibutton_i.h"
 #include <core/log.h>
-#include <dolphin/dolphin.h>
 #include <toolbox/path.h>
 
 #define EMULATE_TIMEOUT_TICKS 10
@@ -26,8 +25,6 @@ void ibutton_scene_emulate_on_enter(void* context) {
         path_extract_filename(ibutton->file_path, key_name, true);
     }
 
-    DOLPHIN_DEED(DolphinDeedIbuttonEmulate);
-
     // check that stored key has name
     if(!furi_string_empty(key_name)) {
         ibutton_text_store_set(ibutton, "%s", furi_string_get_cstr(key_name));

+ 1 - 2
applications/main/ibutton/scenes/ibutton_scene_read.c

@@ -11,7 +11,6 @@ void ibutton_scene_read_on_enter(void* context) {
     Popup* popup = ibutton->popup;
     iButtonKey* key = ibutton->key;
     iButtonWorker* worker = ibutton->key_worker;
-    DOLPHIN_DEED(DolphinDeedIbuttonRead);
 
     popup_set_header(popup, "iButton", 95, 26, AlignCenter, AlignBottom);
     popup_set_text(popup, "Waiting\nfor key ...", 95, 30, AlignCenter, AlignTop);
@@ -54,8 +53,8 @@ bool ibutton_scene_read_on_event(void* context, SceneManagerEvent event) {
             if(success) {
                 ibutton_notification_message(ibutton, iButtonNotificationMessageSuccess);
                 ibutton_notification_message(ibutton, iButtonNotificationMessageGreenOn);
-                DOLPHIN_DEED(DolphinDeedIbuttonReadSuccess);
                 scene_manager_next_scene(scene_manager, iButtonSceneReadSuccess);
+                DOLPHIN_DEED(DolphinDeedIbuttonReadSuccess);
             }
         }
     }

+ 2 - 0
applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c

@@ -1,4 +1,5 @@
 #include "../ibutton_i.h"
+#include <dolphin/dolphin.h>
 
 typedef enum {
     SubmenuIndexSave,
@@ -49,6 +50,7 @@ bool ibutton_scene_read_key_menu_on_event(void* context, SceneManagerEvent event
             scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSaveName);
         } else if(event.event == SubmenuIndexEmulate) {
             scene_manager_next_scene(ibutton->scene_manager, iButtonSceneEmulate);
+            DOLPHIN_DEED(DolphinDeedIbuttonEmulate);
         } else if(event.event == SubmenuIndexWrite) {
             scene_manager_next_scene(ibutton->scene_manager, iButtonSceneWrite);
         }

+ 10 - 0
applications/main/ibutton/scenes/ibutton_scene_save_name.c

@@ -1,6 +1,7 @@
 #include "../ibutton_i.h"
 #include <lib/toolbox/random_name.h>
 #include <toolbox/path.h>
+#include <dolphin/dolphin.h>
 
 static void ibutton_scene_save_name_text_input_callback(void* context) {
     iButton* ibutton = context;
@@ -57,6 +58,15 @@ bool ibutton_scene_save_name_on_event(void* context, SceneManagerEvent event) {
         if(event.event == iButtonCustomEventTextEditResult) {
             if(ibutton_save_key(ibutton, ibutton->text_store)) {
                 scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSaveSuccess);
+                if(scene_manager_has_previous_scene(
+                       ibutton->scene_manager, iButtonSceneSavedKeyMenu)) {
+                    // Nothing, do not count editing as saving
+                } else if(scene_manager_has_previous_scene(
+                              ibutton->scene_manager, iButtonSceneAddType)) {
+                    DOLPHIN_DEED(DolphinDeedIbuttonAdd);
+                } else {
+                    DOLPHIN_DEED(DolphinDeedIbuttonSave);
+                }
             } else {
                 const uint32_t possible_scenes[] = {
                     iButtonSceneReadKeyMenu, iButtonSceneSavedKeyMenu, iButtonSceneAddType};

+ 0 - 2
applications/main/ibutton/scenes/ibutton_scene_save_success.c

@@ -1,5 +1,4 @@
 #include "../ibutton_i.h"
-#include <dolphin/dolphin.h>
 
 static void ibutton_scene_save_success_popup_callback(void* context) {
     iButton* ibutton = context;
@@ -9,7 +8,6 @@ static void ibutton_scene_save_success_popup_callback(void* context) {
 void ibutton_scene_save_success_on_enter(void* context) {
     iButton* ibutton = context;
     Popup* popup = ibutton->popup;
-    DOLPHIN_DEED(DolphinDeedIbuttonSave);
 
     popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
     popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop);

+ 2 - 0
applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c

@@ -1,4 +1,5 @@
 #include "../ibutton_i.h"
+#include <dolphin/dolphin.h>
 
 enum SubmenuIndex {
     SubmenuIndexEmulate,
@@ -58,6 +59,7 @@ bool ibutton_scene_saved_key_menu_on_event(void* context, SceneManagerEvent even
         consumed = true;
         if(event.event == SubmenuIndexEmulate) {
             scene_manager_next_scene(ibutton->scene_manager, iButtonSceneEmulate);
+            DOLPHIN_DEED(DolphinDeedIbuttonEmulate);
         } else if(event.event == SubmenuIndexWrite) {
             scene_manager_next_scene(ibutton->scene_manager, iButtonSceneWrite);
         } else if(event.event == SubmenuIndexEdit) {

+ 2 - 0
applications/main/ibutton/scenes/ibutton_scene_start.c

@@ -1,5 +1,6 @@
 #include "../ibutton_i.h"
 #include "ibutton/scenes/ibutton_scene.h"
+#include <dolphin/dolphin.h>
 
 enum SubmenuIndex {
     SubmenuIndexRead,
@@ -38,6 +39,7 @@ bool ibutton_scene_start_on_event(void* context, SceneManagerEvent event) {
         consumed = true;
         if(event.event == SubmenuIndexRead) {
             scene_manager_next_scene(ibutton->scene_manager, iButtonSceneRead);
+            DOLPHIN_DEED(DolphinDeedIbuttonRead);
         } else if(event.event == SubmenuIndexSaved) {
             furi_string_set(ibutton->file_path, IBUTTON_APP_FOLDER);
             scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSelectKey);

+ 77 - 116
applications/main/infrared/infrared_cli.c

@@ -5,25 +5,21 @@
 #include <furi_hal_infrared.h>
 #include <flipper_format.h>
 #include <toolbox/args.h>
+#include <m-dict.h>
 
 #include "infrared_signal.h"
 #include "infrared_brute_force.h"
 
-#include <m-dict.h>
-
 #define INFRARED_CLI_BUF_SIZE 10
+#define INFRARED_ASSETS_FOLDER "infrared/assets"
+#define INFRARED_BRUTE_FORCE_DUMMY_INDEX 0
 
 DICT_DEF2(dict_signals, FuriString*, FURI_STRING_OPLIST, int, M_DEFAULT_OPLIST)
 
-enum RemoteTypes { TV = 0, AC = 1 };
-
 static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args);
 static void infrared_cli_start_ir_tx(Cli* cli, FuriString* args);
 static void infrared_cli_process_decode(Cli* cli, FuriString* args);
 static void infrared_cli_process_universal(Cli* cli, FuriString* args);
-static void infrared_cli_list_remote_signals(enum RemoteTypes remote_type);
-static void
-    infrared_cli_brute_force_signals(Cli* cli, enum RemoteTypes remote_type, FuriString* signal);
 
 static const struct {
     const char* cmd;
@@ -87,8 +83,10 @@ static void infrared_cli_print_usage(void) {
         INFRARED_MIN_FREQUENCY,
         INFRARED_MAX_FREQUENCY);
     printf("\tir decode <input_file> [<output_file>]\r\n");
-    printf("\tir universal <tv, ac> <signal name>\r\n");
-    printf("\tir universal list <tv, ac>\r\n");
+    printf("\tir universal <remote_name> <signal_name>\r\n");
+    printf("\tir universal list <remote_name>\r\n");
+    // TODO: Do not hardcode universal remote names
+    printf("\tAvailable universal remotes: tv audio ac\r\n");
 }
 
 static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) {
@@ -356,89 +354,31 @@ static void infrared_cli_process_decode(Cli* cli, FuriString* args) {
     furi_record_close(RECORD_STORAGE);
 }
 
-static void infrared_cli_process_universal(Cli* cli, FuriString* args) {
-    enum RemoteTypes Remote;
+static void infrared_cli_list_remote_signals(FuriString* remote_name) {
+    if(furi_string_empty(remote_name)) {
+        printf("Missing remote name.\r\n");
+        return;
+    }
 
-    FuriString* command;
-    FuriString* remote;
-    FuriString* signal;
-    command = furi_string_alloc();
-    remote = furi_string_alloc();
-    signal = furi_string_alloc();
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
+    FuriString* remote_path = furi_string_alloc_printf(
+        "%s/%s.ir", EXT_PATH(INFRARED_ASSETS_FOLDER), furi_string_get_cstr(remote_name));
 
     do {
-        if(!args_read_string_and_trim(args, command)) {
-            infrared_cli_print_usage();
+        if(!flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(remote_path))) {
+            printf("Invalid remote name.\r\n");
             break;
         }
 
-        if(furi_string_cmp_str(command, "list") == 0) {
-            args_read_string_and_trim(args, remote);
-            if(furi_string_cmp_str(remote, "tv") == 0) {
-                Remote = TV;
-            } else if(furi_string_cmp_str(remote, "ac") == 0) {
-                Remote = AC;
-            } else {
-                printf("Invalid remote type.\r\n");
-                break;
-            }
-            infrared_cli_list_remote_signals(Remote);
-            break;
-        }
-
-        if(furi_string_cmp_str(command, "tv") == 0) {
-            Remote = TV;
-        } else if(furi_string_cmp_str(command, "ac") == 0) {
-            Remote = AC;
-        } else {
-            printf("Invalid remote type.\r\n");
-            break;
-        }
-
-        args_read_string_and_trim(args, signal);
-        if(furi_string_empty(signal)) {
-            printf("Must supply a valid signal for type of remote selected.\r\n");
-            break;
-        }
-
-        infrared_cli_brute_force_signals(cli, Remote, signal);
-        break;
-
-    } while(false);
-
-    furi_string_free(command);
-    furi_string_free(remote);
-    furi_string_free(signal);
-}
-
-static void infrared_cli_list_remote_signals(enum RemoteTypes remote_type) {
-    Storage* storage = furi_record_open(RECORD_STORAGE);
-    FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
-    dict_signals_t signals_dict;
-    FuriString* key;
-    const char* remote_file = NULL;
-    bool success = false;
-    int max = 1;
-
-    switch(remote_type) {
-    case TV:
-        remote_file = EXT_PATH("infrared/assets/tv.ir");
-        break;
-    case AC:
-        remote_file = EXT_PATH("infrared/assets/ac.ir");
-        break;
-    default:
-        break;
-    }
+        dict_signals_t signals_dict;
+        dict_signals_init(signals_dict);
 
-    dict_signals_init(signals_dict);
-    key = furi_string_alloc();
+        FuriString* key = furi_string_alloc();
+        FuriString* signal_name = furi_string_alloc();
 
-    success = flipper_format_buffered_file_open_existing(ff, remote_file);
-    if(success) {
-        FuriString* signal_name;
-        signal_name = furi_string_alloc();
         printf("Valid signals:\r\n");
+        int max = 1;
         while(flipper_format_read_string(ff, "name", signal_name)) {
             furi_string_set_str(key, furi_string_get_cstr(signal_name));
             int* v = dict_signals_get(signals_dict, key);
@@ -449,57 +389,57 @@ static void infrared_cli_list_remote_signals(enum RemoteTypes remote_type) {
                 dict_signals_set_at(signals_dict, key, 1);
             }
         }
+
         dict_signals_it_t it;
         for(dict_signals_it(it, signals_dict); !dict_signals_end_p(it); dict_signals_next(it)) {
             const struct dict_signals_pair_s* pair = dict_signals_cref(it);
             printf("\t%s\r\n", furi_string_get_cstr(pair->key));
         }
+
+        furi_string_free(key);
         furi_string_free(signal_name);
-    }
+        dict_signals_clear(signals_dict);
+
+    } while(false);
 
-    furi_string_free(key);
-    dict_signals_clear(signals_dict);
     flipper_format_free(ff);
+    furi_string_free(remote_path);
     furi_record_close(RECORD_STORAGE);
 }
 
 static void
-    infrared_cli_brute_force_signals(Cli* cli, enum RemoteTypes remote_type, FuriString* signal) {
+    infrared_cli_brute_force_signals(Cli* cli, FuriString* remote_name, FuriString* signal_name) {
     InfraredBruteForce* brute_force = infrared_brute_force_alloc();
-    const char* remote_file = NULL;
-    uint32_t i = 0;
-    bool success = false;
-
-    switch(remote_type) {
-    case TV:
-        remote_file = EXT_PATH("infrared/assets/tv.ir");
-        break;
-    case AC:
-        remote_file = EXT_PATH("infrared/assets/ac.ir");
-        break;
-    default:
-        break;
-    }
+    FuriString* remote_path = furi_string_alloc_printf(
+        "%s/%s.ir", EXT_PATH(INFRARED_ASSETS_FOLDER), furi_string_get_cstr(remote_name));
 
-    infrared_brute_force_set_db_filename(brute_force, remote_file);
-    infrared_brute_force_add_record(brute_force, i++, furi_string_get_cstr(signal));
+    infrared_brute_force_set_db_filename(brute_force, furi_string_get_cstr(remote_path));
+    infrared_brute_force_add_record(
+        brute_force, INFRARED_BRUTE_FORCE_DUMMY_INDEX, furi_string_get_cstr(signal_name));
+
+    do {
+        if(furi_string_empty(signal_name)) {
+            printf("Missing signal name.\r\n");
+            break;
+        }
+        if(!infrared_brute_force_calculate_messages(brute_force)) {
+            printf("Invalid remote name.\r\n");
+            break;
+        }
 
-    success = infrared_brute_force_calculate_messages(brute_force);
-    if(success) {
         uint32_t record_count;
-        uint32_t index = 0;
-        int records_sent = 0;
-        bool running = false;
+        bool running = infrared_brute_force_start(
+            brute_force, INFRARED_BRUTE_FORCE_DUMMY_INDEX, &record_count);
 
-        running = infrared_brute_force_start(brute_force, index, &record_count);
         if(record_count <= 0) {
-            printf("Invalid signal.\n");
-            infrared_brute_force_reset(brute_force);
-            return;
+            printf("Invalid signal name.\r\n");
+            break;
         }
 
-        printf("Sending %ld codes to the tv.\r\n", record_count);
+        printf("Sending %ld signal(s)...\r\n", record_count);
         printf("Press Ctrl-C to stop.\r\n");
+
+        int records_sent = 0;
         while(running) {
             running = infrared_brute_force_send_next(brute_force);
 
@@ -510,14 +450,35 @@ static void
         }
 
         infrared_brute_force_stop(brute_force);
-    } else {
-        printf("Invalid signal.\r\n");
-    }
+    } while(false);
 
+    furi_string_free(remote_path);
     infrared_brute_force_reset(brute_force);
     infrared_brute_force_free(brute_force);
 }
 
+static void infrared_cli_process_universal(Cli* cli, FuriString* args) {
+    FuriString* arg1 = furi_string_alloc();
+    FuriString* arg2 = furi_string_alloc();
+
+    do {
+        if(!args_read_string_and_trim(args, arg1)) break;
+        if(!args_read_string_and_trim(args, arg2)) break;
+    } while(false);
+
+    if(furi_string_empty(arg1)) {
+        printf("Wrong arguments.\r\n");
+        infrared_cli_print_usage();
+    } else if(furi_string_equal_str(arg1, "list")) {
+        infrared_cli_list_remote_signals(arg2);
+    } else {
+        infrared_cli_brute_force_signals(cli, arg1, arg2);
+    }
+
+    furi_string_free(arg1);
+    furi_string_free(arg2);
+}
+
 static void infrared_cli_start_ir(Cli* cli, FuriString* args, void* context) {
     UNUSED(context);
     if(furi_hal_infrared_is_busy()) {

+ 1 - 0
applications/main/infrared/infrared_i.h

@@ -2,6 +2,7 @@
 
 #include <gui/gui.h>
 #include <gui/view.h>
+#include <assets_icons.h>
 #include <gui/view_stack.h>
 #include <gui/view_dispatcher.h>
 #include <gui/scene_manager.h>

+ 1 - 0
applications/main/infrared/scenes/infrared_scene_config.h

@@ -16,6 +16,7 @@ ADD_SCENE(infrared, remote_list, RemoteList)
 ADD_SCENE(infrared, universal, Universal)
 ADD_SCENE(infrared, universal_tv, UniversalTV)
 ADD_SCENE(infrared, universal_ac, UniversalAC)
+ADD_SCENE(infrared, universal_audio, UniversalAudio)
 ADD_SCENE(infrared, debug, Debug)
 ADD_SCENE(infrared, error_databases, ErrorDatabases)
 ADD_SCENE(infrared, rpc, Rpc)

+ 2 - 0
applications/main/infrared/scenes/infrared_scene_learn.c

@@ -1,4 +1,5 @@
 #include "../infrared_i.h"
+#include <dolphin/dolphin.h>
 
 void infrared_scene_learn_on_enter(void* context) {
     Infrared* infrared = context;
@@ -27,6 +28,7 @@ bool infrared_scene_learn_on_event(void* context, SceneManagerEvent event) {
         if(event.event == InfraredCustomEventTypeSignalReceived) {
             infrared_play_notification_message(infrared, InfraredNotificationMessageSuccess);
             scene_manager_next_scene(infrared->scene_manager, InfraredSceneLearnSuccess);
+            DOLPHIN_DEED(DolphinDeedIrLearnSuccess);
             consumed = true;
         }
     }

+ 0 - 3
applications/main/infrared/scenes/infrared_scene_learn_done.c

@@ -1,13 +1,10 @@
 #include "../infrared_i.h"
 
-#include <dolphin/dolphin.h>
-
 void infrared_scene_learn_done_on_enter(void* context) {
     Infrared* infrared = context;
     Popup* popup = infrared->popup;
 
     popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
-    DOLPHIN_DEED(DolphinDeedIrSave);
 
     if(infrared->app_state.is_learning_new_remote) {
         popup_set_header(popup, "New remote\ncreated!", 0, 0, AlignLeft, AlignTop);

+ 2 - 0
applications/main/infrared/scenes/infrared_scene_learn_enter_name.c

@@ -1,4 +1,5 @@
 #include "../infrared_i.h"
+#include <dolphin/dolphin.h>
 
 void infrared_scene_learn_enter_name_on_enter(void* context) {
     Infrared* infrared = context;
@@ -49,6 +50,7 @@ bool infrared_scene_learn_enter_name_on_event(void* context, SceneManagerEvent e
 
             if(success) {
                 scene_manager_next_scene(scene_manager, InfraredSceneLearnDone);
+                DOLPHIN_DEED(DolphinDeedIrSave);
             } else {
                 dialog_message_show_storage_error(infrared->dialogs, "Failed to save file");
                 const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart};

+ 0 - 3
applications/main/infrared/scenes/infrared_scene_learn_success.c

@@ -1,7 +1,5 @@
 #include "../infrared_i.h"
 
-#include <dolphin/dolphin.h>
-
 static void
     infrared_scene_learn_success_dialog_result_callback(DialogExResult result, void* context) {
     Infrared* infrared = context;
@@ -13,7 +11,6 @@ void infrared_scene_learn_success_on_enter(void* context) {
     DialogEx* dialog_ex = infrared->dialog_ex;
     InfraredSignal* signal = infrared->received_signal;
 
-    DOLPHIN_DEED(DolphinDeedIrLearnSuccess);
     infrared_play_notification_message(infrared, InfraredNotificationMessageGreenOn);
 
     if(infrared_signal_is_raw(signal)) {

+ 7 - 1
applications/main/infrared/scenes/infrared_scene_universal.c

@@ -21,6 +21,12 @@ void infrared_scene_universal_on_enter(void* context) {
         SubmenuIndexUniversalTV,
         infrared_scene_universal_submenu_callback,
         context);
+    submenu_add_item(
+        submenu,
+        "Audio Players",
+        SubmenuIndexUniversalAudio,
+        infrared_scene_universal_submenu_callback,
+        context);
     submenu_add_item(
         submenu,
         "Air Conditioners",
@@ -45,7 +51,7 @@ bool infrared_scene_universal_on_event(void* context, SceneManagerEvent event) {
             scene_manager_next_scene(scene_manager, InfraredSceneUniversalAC);
             consumed = true;
         } else if(event.event == SubmenuIndexUniversalAudio) {
-            //TODO Implement Audio universal remote
+            scene_manager_next_scene(scene_manager, InfraredSceneUniversalAudio);
             consumed = true;
         }
     }

+ 133 - 0
applications/main/infrared/scenes/infrared_scene_universal_audio.c

@@ -0,0 +1,133 @@
+#include "../infrared_i.h"
+
+#include "common/infrared_scene_universal_common.h"
+
+void infrared_scene_universal_audio_on_enter(void* context) {
+    infrared_scene_universal_common_on_enter(context);
+
+    Infrared* infrared = context;
+    ButtonPanel* button_panel = infrared->button_panel;
+    InfraredBruteForce* brute_force = infrared->brute_force;
+
+    infrared_brute_force_set_db_filename(brute_force, EXT_PATH("infrared/assets/audio.ir"));
+
+    button_panel_reserve(button_panel, 2, 4);
+    uint32_t i = 0;
+    button_panel_add_item(
+        button_panel,
+        i,
+        0,
+        0,
+        3,
+        11,
+        &I_Power_25x27,
+        &I_Power_hvr_25x27,
+        infrared_scene_universal_common_item_callback,
+        context);
+    infrared_brute_force_add_record(brute_force, i++, "Power");
+    button_panel_add_item(
+        button_panel,
+        i,
+        1,
+        0,
+        36,
+        11,
+        &I_Mute_25x27,
+        &I_Mute_hvr_25x27,
+        infrared_scene_universal_common_item_callback,
+        context);
+    infrared_brute_force_add_record(brute_force, i++, "Mute");
+    button_panel_add_item(
+        button_panel,
+        i,
+        0,
+        1,
+        3,
+        41,
+        &I_Play_25x27,
+        &I_Play_hvr_25x27,
+        infrared_scene_universal_common_item_callback,
+        context);
+    infrared_brute_force_add_record(brute_force, i++, "Play");
+    button_panel_add_item(
+        button_panel,
+        i,
+        1,
+        1,
+        36,
+        41,
+        &I_Pause_25x27,
+        &I_Pause_hvr_25x27,
+        infrared_scene_universal_common_item_callback,
+        context);
+    infrared_brute_force_add_record(brute_force, i++, "Pause");
+    button_panel_add_item(
+        button_panel,
+        i,
+        0,
+        2,
+        3,
+        71,
+        &I_TrackPrev_25x27,
+        &I_TrackPrev_hvr_25x27,
+        infrared_scene_universal_common_item_callback,
+        context);
+    infrared_brute_force_add_record(brute_force, i++, "Prev");
+    button_panel_add_item(
+        button_panel,
+        i,
+        1,
+        2,
+        36,
+        71,
+        &I_TrackNext_25x27,
+        &I_TrackNext_hvr_25x27,
+        infrared_scene_universal_common_item_callback,
+        context);
+    infrared_brute_force_add_record(brute_force, i++, "Next");
+    button_panel_add_item(
+        button_panel,
+        i,
+        0,
+        3,
+        3,
+        101,
+        &I_Vol_down_25x27,
+        &I_Vol_down_hvr_25x27,
+        infrared_scene_universal_common_item_callback,
+        context);
+    infrared_brute_force_add_record(brute_force, i++, "Vol_dn");
+    button_panel_add_item(
+        button_panel,
+        i,
+        1,
+        3,
+        36,
+        101,
+        &I_Vol_up_25x27,
+        &I_Vol_up_hvr_25x27,
+        infrared_scene_universal_common_item_callback,
+        context);
+    infrared_brute_force_add_record(brute_force, i++, "Vol_up");
+
+    button_panel_add_label(button_panel, 1, 8, FontPrimary, "Mus. remote");
+
+    view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical);
+    view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
+
+    infrared_show_loading_popup(infrared, true);
+    bool success = infrared_brute_force_calculate_messages(brute_force);
+    infrared_show_loading_popup(infrared, false);
+
+    if(!success) {
+        scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases);
+    }
+}
+
+bool infrared_scene_universal_audio_on_event(void* context, SceneManagerEvent event) {
+    return infrared_scene_universal_common_on_event(context, event);
+}
+
+void infrared_scene_universal_audio_on_exit(void* context) {
+    infrared_scene_universal_common_on_exit(context);
+}

+ 4 - 1
applications/main/lfrfid/lfrfid.c

@@ -1,4 +1,5 @@
 #include "lfrfid_i.h"
+#include <dolphin/dolphin.h>
 
 static bool lfrfid_debug_custom_event_callback(void* context, uint32_t event) {
     furi_assert(context);
@@ -182,12 +183,14 @@ int32_t lfrfid_app(void* p) {
             view_dispatcher_attach_to_gui(
                 app->view_dispatcher, app->gui, ViewDispatcherTypeDesktop);
             scene_manager_next_scene(app->scene_manager, LfRfidSceneRpc);
+            DOLPHIN_DEED(DolphinDeedRfidEmulate);
         } else {
             furi_string_set(app->file_path, args);
             lfrfid_load_key_data(app, app->file_path, true);
             view_dispatcher_attach_to_gui(
                 app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
             scene_manager_next_scene(app->scene_manager, LfRfidSceneEmulate);
+            DOLPHIN_DEED(DolphinDeedRfidEmulate);
         }
 
     } else {
@@ -311,4 +314,4 @@ void lfrfid_widget_callback(GuiButtonType result, InputType type, void* context)
 void lfrfid_text_input_callback(void* context) {
     LfRfid* app = context;
     view_dispatcher_send_custom_event(app->view_dispatcher, LfRfidEventNext);
-}
+}

+ 1 - 0
applications/main/lfrfid/lfrfid_i.h

@@ -5,6 +5,7 @@
 
 #include <gui/gui.h>
 #include <gui/view.h>
+#include <assets_icons.h>
 #include <gui/view_dispatcher.h>
 #include <gui/scene_manager.h>
 #include <cli/cli.h>

+ 0 - 3
applications/main/lfrfid/scenes/lfrfid_scene_emulate.c

@@ -1,12 +1,9 @@
 #include "../lfrfid_i.h"
-#include <dolphin/dolphin.h>
 
 void lfrfid_scene_emulate_on_enter(void* context) {
     LfRfid* app = context;
     Popup* popup = app->popup;
 
-    DOLPHIN_DEED(DolphinDeedRfidEmulate);
-
     popup_set_header(popup, "Emulating", 89, 30, AlignCenter, AlignTop);
     if(!furi_string_empty(app->file_name)) {
         popup_set_text(popup, furi_string_get_cstr(app->file_name), 89, 43, AlignCenter, AlignTop);

+ 3 - 0
applications/main/lfrfid/scenes/lfrfid_scene_extra_actions.c

@@ -1,4 +1,5 @@
 #include "../lfrfid_i.h"
+#include <dolphin/dolphin.h>
 
 typedef enum {
     SubmenuIndexASK,
@@ -57,10 +58,12 @@ bool lfrfid_scene_extra_actions_on_event(void* context, SceneManagerEvent event)
         if(event.event == SubmenuIndexASK) {
             app->read_type = LFRFIDWorkerReadTypeASKOnly;
             scene_manager_next_scene(app->scene_manager, LfRfidSceneRead);
+            DOLPHIN_DEED(DolphinDeedRfidRead);
             consumed = true;
         } else if(event.event == SubmenuIndexPSK) {
             app->read_type = LFRFIDWorkerReadTypePSKOnly;
             scene_manager_next_scene(app->scene_manager, LfRfidSceneRead);
+            DOLPHIN_DEED(DolphinDeedRfidRead);
             consumed = true;
         } else if(event.event == SubmenuIndexRAW) {
             scene_manager_next_scene(app->scene_manager, LfRfidSceneRawName);

+ 1 - 2
applications/main/lfrfid/scenes/lfrfid_scene_read.c

@@ -46,7 +46,6 @@ static void
 void lfrfid_scene_read_on_enter(void* context) {
     LfRfid* app = context;
 
-    DOLPHIN_DEED(DolphinDeedRfidRead);
     if(app->read_type == LFRFIDWorkerReadTypePSKOnly) {
         lfrfid_view_read_set_read_mode(app->read_view, LfRfidReadPskOnly);
     } else if(app->read_type == LFRFIDWorkerReadTypeASKOnly) {
@@ -79,10 +78,10 @@ bool lfrfid_scene_read_on_event(void* context, SceneManagerEvent event) {
             consumed = true;
         } else if(event.event == LfRfidEventReadDone) {
             app->protocol_id = app->protocol_id_next;
-            DOLPHIN_DEED(DolphinDeedRfidReadSuccess);
             notification_message(app->notifications, &sequence_success);
             furi_string_reset(app->file_name);
             scene_manager_next_scene(app->scene_manager, LfRfidSceneReadSuccess);
+            DOLPHIN_DEED(DolphinDeedRfidReadSuccess);
             consumed = true;
         } else if(event.event == LfRfidEventReadStartPSK) {
             if(app->read_type == LFRFIDWorkerReadTypeAuto) {

+ 2 - 0
applications/main/lfrfid/scenes/lfrfid_scene_read_key_menu.c

@@ -1,4 +1,5 @@
 #include "../lfrfid_i.h"
+#include <dolphin/dolphin.h>
 
 typedef enum {
     SubmenuIndexSave,
@@ -43,6 +44,7 @@ bool lfrfid_scene_read_key_menu_on_event(void* context, SceneManagerEvent event)
             consumed = true;
         } else if(event.event == SubmenuIndexEmulate) {
             scene_manager_next_scene(app->scene_manager, LfRfidSceneEmulate);
+            DOLPHIN_DEED(DolphinDeedRfidEmulate);
             consumed = true;
         }
         scene_manager_set_scene_state(app->scene_manager, LfRfidSceneReadKeyMenu, event.event);

+ 0 - 2
applications/main/lfrfid/scenes/lfrfid_scene_save_data.c

@@ -1,5 +1,4 @@
 #include "../lfrfid_i.h"
-#include <dolphin/dolphin.h>
 
 void lfrfid_scene_save_data_on_enter(void* context) {
     LfRfid* app = context;
@@ -32,7 +31,6 @@ bool lfrfid_scene_save_data_on_event(void* context, SceneManagerEvent event) {
             consumed = true;
             size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id);
             protocol_dict_set_data(app->dict, app->protocol_id, app->new_key_data, size);
-            DOLPHIN_DEED(DolphinDeedRfidAdd);
             scene_manager_next_scene(scene_manager, LfRfidSceneSaveName);
             scene_manager_set_scene_state(scene_manager, LfRfidSceneSaveData, 1);
         }

+ 8 - 0
applications/main/lfrfid/scenes/lfrfid_scene_save_name.c

@@ -1,5 +1,6 @@
 #include <lib/toolbox/random_name.h>
 #include "../lfrfid_i.h"
+#include <dolphin/dolphin.h>
 
 void lfrfid_scene_save_name_on_enter(void* context) {
     LfRfid* app = context;
@@ -55,6 +56,13 @@ bool lfrfid_scene_save_name_on_event(void* context, SceneManagerEvent event) {
 
             if(lfrfid_save_key(app)) {
                 scene_manager_next_scene(scene_manager, LfRfidSceneSaveSuccess);
+                if(scene_manager_has_previous_scene(scene_manager, LfRfidSceneSavedKeyMenu)) {
+                    // Nothing, do not count editing as saving
+                } else if(scene_manager_has_previous_scene(scene_manager, LfRfidSceneSaveType)) {
+                    DOLPHIN_DEED(DolphinDeedRfidAdd);
+                } else {
+                    DOLPHIN_DEED(DolphinDeedRfidSave);
+                }
             } else {
                 scene_manager_search_and_switch_to_previous_scene(
                     scene_manager, LfRfidSceneReadKeyMenu);

+ 0 - 2
applications/main/lfrfid/scenes/lfrfid_scene_save_success.c

@@ -1,5 +1,4 @@
 #include "../lfrfid_i.h"
-#include <dolphin/dolphin.h>
 
 void lfrfid_scene_save_success_on_enter(void* context) {
     LfRfid* app = context;
@@ -8,7 +7,6 @@ void lfrfid_scene_save_success_on_enter(void* context) {
     // Clear state of data enter scene
     scene_manager_set_scene_state(app->scene_manager, LfRfidSceneSaveData, 0);
 
-    DOLPHIN_DEED(DolphinDeedRfidSave);
     popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
     popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop);
     popup_set_context(popup, app);

+ 2 - 0
applications/main/lfrfid/scenes/lfrfid_scene_saved_key_menu.c

@@ -1,4 +1,5 @@
 #include "../lfrfid_i.h"
+#include <dolphin/dolphin.h>
 
 typedef enum {
     SubmenuIndexEmulate,
@@ -42,6 +43,7 @@ bool lfrfid_scene_saved_key_menu_on_event(void* context, SceneManagerEvent event
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SubmenuIndexEmulate) {
             scene_manager_next_scene(app->scene_manager, LfRfidSceneEmulate);
+            DOLPHIN_DEED(DolphinDeedRfidEmulate);
             consumed = true;
         } else if(event.event == SubmenuIndexWrite) {
             scene_manager_next_scene(app->scene_manager, LfRfidSceneWrite);

+ 2 - 0
applications/main/lfrfid/scenes/lfrfid_scene_start.c

@@ -1,4 +1,5 @@
 #include "../lfrfid_i.h"
+#include <dolphin/dolphin.h>
 
 typedef enum {
     SubmenuIndexRead,
@@ -47,6 +48,7 @@ bool lfrfid_scene_start_on_event(void* context, SceneManagerEvent event) {
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SubmenuIndexRead) {
             scene_manager_next_scene(app->scene_manager, LfRfidSceneRead);
+            DOLPHIN_DEED(DolphinDeedRfidRead);
             consumed = true;
         } else if(event.event == SubmenuIndexSaved) {
             furi_string_set(app->file_path, LFRFID_APP_FOLDER);

+ 1 - 0
applications/main/lfrfid/views/lfrfid_view_read.c

@@ -1,5 +1,6 @@
 #include "lfrfid_view_read.h"
 #include <gui/elements.h>
+#include <assets_icons.h>
 
 #define TEMP_STR_LEN 128
 

+ 4 - 0
applications/main/nfc/nfc.c

@@ -1,5 +1,6 @@
 #include "nfc_i.h"
 #include "furi_hal_nfc.h"
+#include <dolphin/dolphin.h>
 
 bool nfc_custom_event_callback(void* context, uint32_t event) {
     furi_assert(context);
@@ -275,12 +276,15 @@ int32_t nfc_app(void* p) {
             if(nfc_device_load(nfc->dev, p, true)) {
                 if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) {
                     scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightEmulate);
+                    DOLPHIN_DEED(DolphinDeedNfcEmulate);
                 } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) {
                     scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate);
+                    DOLPHIN_DEED(DolphinDeedNfcEmulate);
                 } else if(nfc->dev->format == NfcDeviceSaveFormatBankCard) {
                     scene_manager_next_scene(nfc->scene_manager, NfcSceneDeviceInfo);
                 } else {
                     scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid);
+                    DOLPHIN_DEED(DolphinDeedNfcEmulate);
                 }
             } else {
                 // Exit app

+ 1 - 0
applications/main/nfc/nfc_i.h

@@ -7,6 +7,7 @@
 
 #include <gui/gui.h>
 #include <gui/view.h>
+#include <assets_icons.h>
 #include <gui/view_dispatcher.h>
 #include <gui/scene_manager.h>
 #include <cli/cli.h>

+ 6 - 0
applications/main/nfc/scenes/nfc_scene_config.h

@@ -36,6 +36,12 @@ ADD_SCENE(nfc, mf_classic_keys_list, MfClassicKeysList)
 ADD_SCENE(nfc, mf_classic_keys_delete, MfClassicKeysDelete)
 ADD_SCENE(nfc, mf_classic_keys_warn_duplicate, MfClassicKeysWarnDuplicate)
 ADD_SCENE(nfc, mf_classic_dict_attack, MfClassicDictAttack)
+ADD_SCENE(nfc, mf_classic_write, MfClassicWrite)
+ADD_SCENE(nfc, mf_classic_write_success, MfClassicWriteSuccess)
+ADD_SCENE(nfc, mf_classic_write_fail, MfClassicWriteFail)
+ADD_SCENE(nfc, mf_classic_update, MfClassicUpdate)
+ADD_SCENE(nfc, mf_classic_update_success, MfClassicUpdateSuccess)
+ADD_SCENE(nfc, mf_classic_wrong_card, MfClassicWrongCard)
 ADD_SCENE(nfc, emv_read_success, EmvReadSuccess)
 ADD_SCENE(nfc, emv_menu, EmvMenu)
 ADD_SCENE(nfc, emulate_apdu_sequence, EmulateApduSequence)

+ 5 - 2
applications/main/nfc/scenes/nfc_scene_detect_reader.c

@@ -1,5 +1,4 @@
 #include "../nfc_i.h"
-#include <dolphin/dolphin.h>
 
 #define NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX (10U)
 
@@ -26,10 +25,14 @@ void nfc_scene_detect_reader_callback(void* context) {
 
 void nfc_scene_detect_reader_on_enter(void* context) {
     Nfc* nfc = context;
-    DOLPHIN_DEED(DolphinDeedNfcDetectReader);
 
     detect_reader_set_callback(nfc->detect_reader, nfc_scene_detect_reader_callback, nfc);
     detect_reader_set_nonces_max(nfc->detect_reader, NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX);
+    NfcDeviceData* dev_data = &nfc->dev->dev_data;
+    if(dev_data->nfc_data.uid_len) {
+        detect_reader_set_uid(
+            nfc->detect_reader, dev_data->nfc_data.uid, dev_data->nfc_data.uid_len);
+    }
 
     // Store number of collected nonces in scene state
     scene_manager_set_scene_state(nfc->scene_manager, NfcSceneDetectReader, 0);

+ 0 - 2
applications/main/nfc/scenes/nfc_scene_emulate_uid.c

@@ -1,5 +1,4 @@
 #include "../nfc_i.h"
-#include <dolphin/dolphin.h>
 
 #define NFC_SCENE_EMULATE_UID_LOG_SIZE_MAX (200)
 
@@ -59,7 +58,6 @@ static void nfc_scene_emulate_uid_widget_config(Nfc* nfc, bool data_received) {
 
 void nfc_scene_emulate_uid_on_enter(void* context) {
     Nfc* nfc = context;
-    DOLPHIN_DEED(DolphinDeedNfcEmulate);
 
     // Setup Widget
     nfc_scene_emulate_uid_widget_config(nfc, false);

+ 0 - 2
applications/main/nfc/scenes/nfc_scene_emv_read_success.c

@@ -1,6 +1,5 @@
 #include "../nfc_i.h"
 #include "../helpers/nfc_emv_parser.h"
-#include <dolphin/dolphin.h>
 
 void nfc_scene_emv_read_success_widget_callback(
     GuiButtonType result,
@@ -15,7 +14,6 @@ void nfc_scene_emv_read_success_widget_callback(
 void nfc_scene_emv_read_success_on_enter(void* context) {
     Nfc* nfc = context;
     EmvData* emv_data = &nfc->dev->dev_data.emv_data;
-    DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
 
     // Setup Custom Widget view
     widget_add_button_element(

+ 4 - 0
applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c

@@ -1,4 +1,5 @@
 #include "../nfc_i.h"
+#include <dolphin/dolphin.h>
 
 #define TAG "NfcMfClassicDictAttack"
 
@@ -110,6 +111,7 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent
             } else {
                 notification_message(nfc->notifications, &sequence_success);
                 scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicReadSuccess);
+                DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
                 consumed = true;
             }
         } else if(event.event == NfcWorkerEventAborted) {
@@ -119,6 +121,8 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent
             } else {
                 notification_message(nfc->notifications, &sequence_success);
                 scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicReadSuccess);
+                // Counting failed attempts too
+                DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
                 consumed = true;
             }
         } else if(event.event == NfcWorkerEventCardDetected) {

+ 0 - 2
applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c

@@ -1,5 +1,4 @@
 #include "../nfc_i.h"
-#include <dolphin/dolphin.h>
 
 #define NFC_MF_CLASSIC_DATA_NOT_CHANGED (0UL)
 #define NFC_MF_CLASSIC_DATA_CHANGED (1UL)
@@ -15,7 +14,6 @@ bool nfc_mf_classic_emulate_worker_callback(NfcWorkerEvent event, void* context)
 
 void nfc_scene_mf_classic_emulate_on_enter(void* context) {
     Nfc* nfc = context;
-    DOLPHIN_DEED(DolphinDeedNfcEmulate);
 
     // Setup view
     Popup* popup = nfc->popup;

+ 2 - 0
applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c

@@ -1,4 +1,5 @@
 #include "../nfc_i.h"
+#include <dolphin/dolphin.h>
 
 void nfc_scene_mf_classic_keys_add_byte_input_callback(void* context) {
     Nfc* nfc = context;
@@ -36,6 +37,7 @@ bool nfc_scene_mf_classic_keys_add_on_event(void* context, SceneManagerEvent eve
                         nfc->scene_manager, NfcSceneMfClassicKeysWarnDuplicate);
                 } else if(mf_classic_dict_add_key(dict, nfc->byte_input_store)) {
                     scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveSuccess);
+                    DOLPHIN_DEED(DolphinDeedNfcMfcAdd);
                 } else {
                     scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound);
                 }

+ 5 - 2
applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c

@@ -36,8 +36,6 @@ bool nfc_scene_mf_classic_menu_on_event(void* context, SceneManagerEvent event)
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SubmenuIndexSave) {
-            DOLPHIN_DEED(DolphinDeedNfcMfcAdd);
-
             scene_manager_set_scene_state(
                 nfc->scene_manager, NfcSceneMfClassicMenu, SubmenuIndexSave);
             nfc->dev->format = NfcDeviceSaveFormatMifareClassic;
@@ -49,6 +47,11 @@ bool nfc_scene_mf_classic_menu_on_event(void* context, SceneManagerEvent event)
             scene_manager_set_scene_state(
                 nfc->scene_manager, NfcSceneMfClassicMenu, SubmenuIndexEmulate);
             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate);
+            if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) {
+                DOLPHIN_DEED(DolphinDeedNfcAddEmulate);
+            } else {
+                DOLPHIN_DEED(DolphinDeedNfcEmulate);
+            }
             consumed = true;
         } else if(event.event == SubmenuIndexInfo) {
             scene_manager_set_scene_state(

+ 0 - 3
applications/main/nfc/scenes/nfc_scene_mf_classic_read_success.c

@@ -1,5 +1,4 @@
 #include "../nfc_i.h"
-#include <dolphin/dolphin.h>
 
 void nfc_scene_mf_classic_read_success_widget_callback(
     GuiButtonType result,
@@ -18,8 +17,6 @@ void nfc_scene_mf_classic_read_success_on_enter(void* context) {
     NfcDeviceData* dev_data = &nfc->dev->dev_data;
     MfClassicData* mf_data = &dev_data->mf_classic_data;
 
-    DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
-
     // Setup view
     Widget* widget = nfc->widget;
     widget_add_button_element(

+ 98 - 0
applications/main/nfc/scenes/nfc_scene_mf_classic_update.c

@@ -0,0 +1,98 @@
+#include "../nfc_i.h"
+#include <dolphin/dolphin.h>
+
+enum {
+    NfcSceneMfClassicUpdateStateCardSearch,
+    NfcSceneMfClassicUpdateStateCardFound,
+};
+
+bool nfc_mf_classic_update_worker_callback(NfcWorkerEvent event, void* context) {
+    furi_assert(context);
+
+    Nfc* nfc = context;
+    view_dispatcher_send_custom_event(nfc->view_dispatcher, event);
+
+    return true;
+}
+
+static void nfc_scene_mf_classic_update_setup_view(Nfc* nfc) {
+    Popup* popup = nfc->popup;
+    popup_reset(popup);
+    uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicUpdate);
+
+    if(state == NfcSceneMfClassicUpdateStateCardSearch) {
+        popup_set_text(
+            nfc->popup, "Apply the initial\ncard only", 128, 32, AlignRight, AlignCenter);
+        popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual_60x50);
+    } else {
+        popup_set_header(popup, "Updating\nDon't move...", 52, 32, AlignLeft, AlignCenter);
+        popup_set_icon(popup, 12, 23, &A_Loading_24);
+    }
+
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
+}
+
+void nfc_scene_mf_classic_update_on_enter(void* context) {
+    Nfc* nfc = context;
+    DOLPHIN_DEED(DolphinDeedNfcEmulate);
+
+    scene_manager_set_scene_state(
+        nfc->scene_manager, NfcSceneMfClassicUpdate, NfcSceneMfClassicUpdateStateCardSearch);
+    nfc_scene_mf_classic_update_setup_view(nfc);
+
+    // Setup and start worker
+    nfc_worker_start(
+        nfc->worker,
+        NfcWorkerStateMfClassicUpdate,
+        &nfc->dev->dev_data,
+        nfc_mf_classic_update_worker_callback,
+        nfc);
+    nfc_blink_emulate_start(nfc);
+}
+
+bool nfc_scene_mf_classic_update_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == NfcWorkerEventSuccess) {
+            nfc_worker_stop(nfc->worker);
+            if(nfc_device_save_shadow(nfc->dev, nfc->dev->dev_name)) {
+                scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicUpdateSuccess);
+            } else {
+                scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrongCard);
+            }
+            consumed = true;
+        } else if(event.event == NfcWorkerEventWrongCard) {
+            nfc_worker_stop(nfc->worker);
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrongCard);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventCardDetected) {
+            scene_manager_set_scene_state(
+                nfc->scene_manager,
+                NfcSceneMfClassicUpdate,
+                NfcSceneMfClassicUpdateStateCardFound);
+            nfc_scene_mf_classic_update_setup_view(nfc);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventNoCardDetected) {
+            scene_manager_set_scene_state(
+                nfc->scene_manager,
+                NfcSceneMfClassicUpdate,
+                NfcSceneMfClassicUpdateStateCardSearch);
+            nfc_scene_mf_classic_update_setup_view(nfc);
+            consumed = true;
+        }
+    }
+    return consumed;
+}
+
+void nfc_scene_mf_classic_update_on_exit(void* context) {
+    Nfc* nfc = context;
+    nfc_worker_stop(nfc->worker);
+    scene_manager_set_scene_state(
+        nfc->scene_manager, NfcSceneMfClassicUpdate, NfcSceneMfClassicUpdateStateCardSearch);
+    // Clear view
+    popup_reset(nfc->popup);
+
+    nfc_blink_stop(nfc);
+}

+ 44 - 0
applications/main/nfc/scenes/nfc_scene_mf_classic_update_success.c

@@ -0,0 +1,44 @@
+#include "../nfc_i.h"
+#include <dolphin/dolphin.h>
+
+void nfc_scene_mf_classic_update_success_popup_callback(void* context) {
+    Nfc* nfc = context;
+    view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit);
+}
+
+void nfc_scene_mf_classic_update_success_on_enter(void* context) {
+    Nfc* nfc = context;
+    DOLPHIN_DEED(DolphinDeedNfcSave);
+
+    notification_message(nfc->notifications, &sequence_success);
+
+    Popup* popup = nfc->popup;
+    popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
+    popup_set_header(popup, "Updated!", 11, 20, AlignLeft, AlignBottom);
+    popup_set_timeout(popup, 1500);
+    popup_set_context(popup, nfc);
+    popup_set_callback(popup, nfc_scene_mf_classic_update_success_popup_callback);
+    popup_enable_timeout(popup);
+
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
+}
+
+bool nfc_scene_mf_classic_update_success_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == NfcCustomEventViewExit) {
+            consumed = scene_manager_search_and_switch_to_previous_scene(
+                nfc->scene_manager, NfcSceneFileSelect);
+        }
+    }
+    return consumed;
+}
+
+void nfc_scene_mf_classic_update_success_on_exit(void* context) {
+    Nfc* nfc = context;
+
+    // Clear view
+    popup_reset(nfc->popup);
+}

+ 92 - 0
applications/main/nfc/scenes/nfc_scene_mf_classic_write.c

@@ -0,0 +1,92 @@
+#include "../nfc_i.h"
+#include <dolphin/dolphin.h>
+
+enum {
+    NfcSceneMfClassicWriteStateCardSearch,
+    NfcSceneMfClassicWriteStateCardFound,
+};
+
+bool nfc_mf_classic_write_worker_callback(NfcWorkerEvent event, void* context) {
+    furi_assert(context);
+
+    Nfc* nfc = context;
+    view_dispatcher_send_custom_event(nfc->view_dispatcher, event);
+
+    return true;
+}
+
+static void nfc_scene_mf_classic_write_setup_view(Nfc* nfc) {
+    Popup* popup = nfc->popup;
+    popup_reset(popup);
+    uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicWrite);
+
+    if(state == NfcSceneMfClassicWriteStateCardSearch) {
+        popup_set_text(
+            nfc->popup, "Apply the initial\ncard only", 128, 32, AlignRight, AlignCenter);
+        popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual_60x50);
+    } else {
+        popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter);
+        popup_set_icon(popup, 12, 23, &A_Loading_24);
+    }
+
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
+}
+
+void nfc_scene_mf_classic_write_on_enter(void* context) {
+    Nfc* nfc = context;
+    DOLPHIN_DEED(DolphinDeedNfcEmulate);
+
+    scene_manager_set_scene_state(
+        nfc->scene_manager, NfcSceneMfClassicWrite, NfcSceneMfClassicWriteStateCardSearch);
+    nfc_scene_mf_classic_write_setup_view(nfc);
+
+    // Setup and start worker
+    nfc_worker_start(
+        nfc->worker,
+        NfcWorkerStateMfClassicWrite,
+        &nfc->dev->dev_data,
+        nfc_mf_classic_write_worker_callback,
+        nfc);
+    nfc_blink_emulate_start(nfc);
+}
+
+bool nfc_scene_mf_classic_write_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == NfcWorkerEventSuccess) {
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWriteSuccess);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventFail) {
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWriteFail);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventWrongCard) {
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrongCard);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventCardDetected) {
+            scene_manager_set_scene_state(
+                nfc->scene_manager, NfcSceneMfClassicWrite, NfcSceneMfClassicWriteStateCardFound);
+            nfc_scene_mf_classic_write_setup_view(nfc);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventNoCardDetected) {
+            scene_manager_set_scene_state(
+                nfc->scene_manager, NfcSceneMfClassicWrite, NfcSceneMfClassicWriteStateCardSearch);
+            nfc_scene_mf_classic_write_setup_view(nfc);
+            consumed = true;
+        }
+    }
+    return consumed;
+}
+
+void nfc_scene_mf_classic_write_on_exit(void* context) {
+    Nfc* nfc = context;
+
+    nfc_worker_stop(nfc->worker);
+    scene_manager_set_scene_state(
+        nfc->scene_manager, NfcSceneMfClassicWrite, NfcSceneMfClassicWriteStateCardSearch);
+    // Clear view
+    popup_reset(nfc->popup);
+
+    nfc_blink_stop(nfc);
+}

+ 58 - 0
applications/main/nfc/scenes/nfc_scene_mf_classic_write_fail.c

@@ -0,0 +1,58 @@
+#include "../nfc_i.h"
+
+void nfc_scene_mf_classic_write_fail_widget_callback(
+    GuiButtonType result,
+    InputType type,
+    void* context) {
+    Nfc* nfc = context;
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
+    }
+}
+
+void nfc_scene_mf_classic_write_fail_on_enter(void* context) {
+    Nfc* nfc = context;
+    Widget* widget = nfc->widget;
+
+    notification_message(nfc->notifications, &sequence_error);
+
+    widget_add_icon_element(widget, 72, 17, &I_DolphinCommon_56x48);
+    widget_add_string_element(
+        widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!");
+    widget_add_string_multiline_element(
+        widget,
+        7,
+        17,
+        AlignLeft,
+        AlignTop,
+        FontSecondary,
+        "Not all sectors\nwere written\ncorrectly.");
+
+    widget_add_button_element(
+        widget, GuiButtonTypeLeft, "Finish", nfc_scene_mf_classic_write_fail_widget_callback, nfc);
+
+    // Setup and start worker
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
+}
+
+bool nfc_scene_mf_classic_write_fail_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == GuiButtonTypeLeft) {
+            consumed = scene_manager_search_and_switch_to_previous_scene(
+                nfc->scene_manager, NfcSceneFileSelect);
+        }
+    } else if(event.type == SceneManagerEventTypeBack) {
+        consumed = scene_manager_search_and_switch_to_previous_scene(
+            nfc->scene_manager, NfcSceneSavedMenu);
+    }
+    return consumed;
+}
+
+void nfc_scene_mf_classic_write_fail_on_exit(void* context) {
+    Nfc* nfc = context;
+
+    widget_reset(nfc->widget);
+}

+ 44 - 0
applications/main/nfc/scenes/nfc_scene_mf_classic_write_success.c

@@ -0,0 +1,44 @@
+#include "../nfc_i.h"
+#include <dolphin/dolphin.h>
+
+void nfc_scene_mf_classic_write_success_popup_callback(void* context) {
+    Nfc* nfc = context;
+    view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit);
+}
+
+void nfc_scene_mf_classic_write_success_on_enter(void* context) {
+    Nfc* nfc = context;
+    DOLPHIN_DEED(DolphinDeedNfcSave);
+
+    notification_message(nfc->notifications, &sequence_success);
+
+    Popup* popup = nfc->popup;
+    popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
+    popup_set_header(popup, "Successfully\nwritten", 13, 22, AlignLeft, AlignBottom);
+    popup_set_timeout(popup, 1500);
+    popup_set_context(popup, nfc);
+    popup_set_callback(popup, nfc_scene_mf_classic_write_success_popup_callback);
+    popup_enable_timeout(popup);
+
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
+}
+
+bool nfc_scene_mf_classic_write_success_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == NfcCustomEventViewExit) {
+            consumed = scene_manager_search_and_switch_to_previous_scene(
+                nfc->scene_manager, NfcSceneFileSelect);
+        }
+    }
+    return consumed;
+}
+
+void nfc_scene_mf_classic_write_success_on_exit(void* context) {
+    Nfc* nfc = context;
+
+    // Clear view
+    popup_reset(nfc->popup);
+}

+ 53 - 0
applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c

@@ -0,0 +1,53 @@
+#include "../nfc_i.h"
+
+void nfc_scene_mf_classic_wrong_card_widget_callback(
+    GuiButtonType result,
+    InputType type,
+    void* context) {
+    Nfc* nfc = context;
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
+    }
+}
+
+void nfc_scene_mf_classic_wrong_card_on_enter(void* context) {
+    Nfc* nfc = context;
+    Widget* widget = nfc->widget;
+
+    notification_message(nfc->notifications, &sequence_error);
+
+    widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48);
+    widget_add_string_element(
+        widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card");
+    widget_add_string_multiline_element(
+        widget,
+        4,
+        17,
+        AlignLeft,
+        AlignTop,
+        FontSecondary,
+        "Data management\nis only possible\nwith initial card");
+    widget_add_button_element(
+        widget, GuiButtonTypeLeft, "Retry", nfc_scene_mf_classic_wrong_card_widget_callback, nfc);
+
+    // Setup and start worker
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
+}
+
+bool nfc_scene_mf_classic_wrong_card_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == GuiButtonTypeLeft) {
+            consumed = scene_manager_previous_scene(nfc->scene_manager);
+        }
+    }
+    return consumed;
+}
+
+void nfc_scene_mf_classic_wrong_card_on_exit(void* context) {
+    Nfc* nfc = context;
+
+    widget_reset(nfc->widget);
+}

+ 6 - 0
applications/main/nfc/scenes/nfc_scene_mf_desfire_menu.c

@@ -1,4 +1,5 @@
 #include "../nfc_i.h"
+#include <dolphin/dolphin.h>
 
 enum SubmenuIndex {
     SubmenuIndexSave,
@@ -48,6 +49,11 @@ bool nfc_scene_mf_desfire_menu_on_event(void* context, SceneManagerEvent event)
             consumed = true;
         } else if(event.event == SubmenuIndexEmulateUid) {
             scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid);
+            if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) {
+                DOLPHIN_DEED(DolphinDeedNfcAddEmulate);
+            } else {
+                DOLPHIN_DEED(DolphinDeedNfcEmulate);
+            }
             consumed = true;
         } else if(event.event == SubmenuIndexInfo) {
             scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo);

+ 0 - 2
applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c

@@ -1,5 +1,4 @@
 #include "../nfc_i.h"
-#include <dolphin/dolphin.h>
 
 #define NFC_MF_UL_DATA_NOT_CHANGED (0UL)
 #define NFC_MF_UL_DATA_CHANGED (1UL)
@@ -15,7 +14,6 @@ bool nfc_mf_ultralight_emulate_worker_callback(NfcWorkerEvent event, void* conte
 
 void nfc_scene_mf_ultralight_emulate_on_enter(void* context) {
     Nfc* nfc = context;
-    DOLPHIN_DEED(DolphinDeedNfcEmulate);
 
     // Setup view
     Popup* popup = nfc->popup;

+ 6 - 0
applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c

@@ -1,4 +1,5 @@
 #include "../nfc_i.h"
+#include <dolphin/dolphin.h>
 
 enum SubmenuIndex {
     SubmenuIndexUnlock,
@@ -56,6 +57,11 @@ bool nfc_scene_mf_ultralight_menu_on_event(void* context, SceneManagerEvent even
             consumed = true;
         } else if(event.event == SubmenuIndexEmulate) {
             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightEmulate);
+            if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) {
+                DOLPHIN_DEED(DolphinDeedNfcAddEmulate);
+            } else {
+                DOLPHIN_DEED(DolphinDeedNfcEmulate);
+            }
             consumed = true;
         } else if(event.event == SubmenuIndexUnlock) {
             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockMenu);

+ 0 - 2
applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c

@@ -1,5 +1,4 @@
 #include "../nfc_i.h"
-#include <dolphin/dolphin.h>
 
 typedef enum {
     NfcSceneMfUlReadStateIdle,
@@ -51,7 +50,6 @@ void nfc_scene_mf_ultralight_read_auth_set_state(Nfc* nfc, NfcSceneMfUlReadState
 
 void nfc_scene_mf_ultralight_read_auth_on_enter(void* context) {
     Nfc* nfc = context;
-    DOLPHIN_DEED(DolphinDeedNfcRead);
 
     nfc_device_clear(nfc->dev);
     // Setup view

+ 0 - 4
applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c

@@ -1,5 +1,4 @@
 #include "../nfc_i.h"
-#include <dolphin/dolphin.h>
 
 void nfc_scene_mf_ultralight_read_auth_result_widget_callback(
     GuiButtonType result,
@@ -37,7 +36,6 @@ void nfc_scene_mf_ultralight_read_auth_result_on_enter(void* context) {
     widget_add_string_element(
         widget, 0, 17, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_str));
     if(mf_ul_data->auth_success) {
-        DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
         furi_string_printf(
             temp_str,
             "Password: %02X %02X %02X %02X",
@@ -54,8 +52,6 @@ void nfc_scene_mf_ultralight_read_auth_result_on_enter(void* context) {
             config_pages->auth_data.pack.raw[1]);
         widget_add_string_element(
             widget, 0, 39, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_str));
-    } else {
-        DOLPHIN_DEED(DolphinDeedNfcMfulError);
     }
     furi_string_printf(
         temp_str, "Pages Read: %d/%d", mf_ul_data->data_read / 4, mf_ul_data->data_size / 4);

+ 0 - 2
applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c

@@ -1,5 +1,4 @@
 #include "../nfc_i.h"
-#include <dolphin/dolphin.h>
 
 void nfc_scene_mf_ultralight_read_success_widget_callback(
     GuiButtonType result,
@@ -14,7 +13,6 @@ void nfc_scene_mf_ultralight_read_success_widget_callback(
 
 void nfc_scene_mf_ultralight_read_success_on_enter(void* context) {
     Nfc* nfc = context;
-    DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
 
     // Setup widget view
     FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data;

+ 2 - 0
applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c

@@ -1,4 +1,5 @@
 #include "../nfc_i.h"
+#include <dolphin/dolphin.h>
 
 void nfc_scene_mf_ultralight_unlock_warn_dialog_callback(DialogExResult result, void* context) {
     Nfc* nfc = context;
@@ -30,6 +31,7 @@ bool nfc_scene_mf_ultralight_unlock_warn_on_event(void* context, SceneManagerEve
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == DialogExResultCenter) {
             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadAuth);
+            DOLPHIN_DEED(DolphinDeedNfcRead);
             consumed = true;
         }
     }

+ 6 - 0
applications/main/nfc/scenes/nfc_scene_nfca_menu.c

@@ -1,4 +1,5 @@
 #include "../nfc_i.h"
+#include <dolphin/dolphin.h>
 
 enum SubmenuIndex {
     SubmenuIndexSaveUid,
@@ -41,6 +42,11 @@ bool nfc_scene_nfca_menu_on_event(void* context, SceneManagerEvent event) {
             consumed = true;
         } else if(event.event == SubmenuIndexEmulateUid) {
             scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid);
+            if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) {
+                DOLPHIN_DEED(DolphinDeedNfcAddEmulate);
+            } else {
+                DOLPHIN_DEED(DolphinDeedNfcEmulate);
+            }
             consumed = true;
         } else if(event.event == SubmenuIndexInfo) {
             scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo);

+ 0 - 3
applications/main/nfc/scenes/nfc_scene_nfca_read_success.c

@@ -1,5 +1,4 @@
 #include "../nfc_i.h"
-#include <dolphin/dolphin.h>
 
 void nfc_scene_nfca_read_success_widget_callback(
     GuiButtonType result,
@@ -16,8 +15,6 @@ void nfc_scene_nfca_read_success_widget_callback(
 void nfc_scene_nfca_read_success_on_enter(void* context) {
     Nfc* nfc = context;
 
-    DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
-
     // Setup view
     FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data;
     Widget* widget = nfc->widget;

+ 6 - 1
applications/main/nfc/scenes/nfc_scene_read.c

@@ -39,7 +39,6 @@ void nfc_scene_read_set_state(Nfc* nfc, NfcSceneReadState state) {
 
 void nfc_scene_read_on_enter(void* context) {
     Nfc* nfc = context;
-    DOLPHIN_DEED(DolphinDeedNfcRead);
 
     nfc_device_clear(nfc->dev);
     // Setup view
@@ -62,26 +61,32 @@ bool nfc_scene_read_on_event(void* context, SceneManagerEvent event) {
            (event.event == NfcWorkerEventReadUidNfcV)) {
             notification_message(nfc->notifications, &sequence_success);
             scene_manager_next_scene(nfc->scene_manager, NfcSceneReadCardSuccess);
+            DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
             consumed = true;
         } else if(event.event == NfcWorkerEventReadUidNfcA) {
             notification_message(nfc->notifications, &sequence_success);
             scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcaReadSuccess);
+            DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
             consumed = true;
         } else if(event.event == NfcWorkerEventReadMfUltralight) {
             notification_message(nfc->notifications, &sequence_success);
             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadSuccess);
+            DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
             consumed = true;
         } else if(event.event == NfcWorkerEventReadMfClassicDone) {
             notification_message(nfc->notifications, &sequence_success);
             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicReadSuccess);
+            DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
             consumed = true;
         } else if(event.event == NfcWorkerEventReadMfDesfire) {
             notification_message(nfc->notifications, &sequence_success);
             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfDesfireReadSuccess);
+            DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
             consumed = true;
         } else if(event.event == NfcWorkerEventReadBankCard) {
             notification_message(nfc->notifications, &sequence_success);
             scene_manager_next_scene(nfc->scene_manager, NfcSceneEmvReadSuccess);
+            DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
             consumed = true;
         } else if(event.event == NfcWorkerEventReadMfClassicDictAttackRequired) {
             if(mf_classic_dict_check_presence(MfClassicDictTypeFlipper)) {

+ 0 - 2
applications/main/nfc/scenes/nfc_scene_read_card_success.c

@@ -1,5 +1,4 @@
 #include "../nfc_i.h"
-#include <dolphin/dolphin.h>
 
 void nfc_scene_read_card_success_widget_callback(
     GuiButtonType result,
@@ -18,7 +17,6 @@ void nfc_scene_read_card_success_on_enter(void* context) {
 
     FuriString* temp_str;
     temp_str = furi_string_alloc();
-    DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
 
     // Setup view
     FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data;

+ 8 - 0
applications/main/nfc/scenes/nfc_scene_save_name.c

@@ -2,6 +2,7 @@
 #include <lib/toolbox/random_name.h>
 #include <gui/modules/validators.h>
 #include <toolbox/path.h>
+#include <dolphin/dolphin.h>
 
 void nfc_scene_save_name_text_input_callback(void* context) {
     Nfc* nfc = context;
@@ -63,6 +64,13 @@ bool nfc_scene_save_name_on_event(void* context, SceneManagerEvent event) {
             strlcpy(nfc->dev->dev_name, nfc->text_store, strlen(nfc->text_store) + 1);
             if(nfc_device_save(nfc->dev, nfc->text_store)) {
                 scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveSuccess);
+                if(!scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu)) {
+                    // Nothing, do not count editing as saving
+                } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) {
+                    DOLPHIN_DEED(DolphinDeedNfcAddSave);
+                } else {
+                    DOLPHIN_DEED(DolphinDeedNfcSave);
+                }
                 consumed = true;
             } else {
                 consumed = scene_manager_search_and_switch_to_previous_scene(

+ 0 - 2
applications/main/nfc/scenes/nfc_scene_save_success.c

@@ -1,5 +1,4 @@
 #include "../nfc_i.h"
-#include <dolphin/dolphin.h>
 
 void nfc_scene_save_success_popup_callback(void* context) {
     Nfc* nfc = context;
@@ -8,7 +7,6 @@ void nfc_scene_save_success_popup_callback(void* context) {
 
 void nfc_scene_save_success_on_enter(void* context) {
     Nfc* nfc = context;
-    DOLPHIN_DEED(DolphinDeedNfcSave);
 
     // Setup view
     Popup* popup = nfc->popup;

+ 36 - 0
applications/main/nfc/scenes/nfc_scene_saved_menu.c

@@ -1,8 +1,12 @@
 #include "../nfc_i.h"
+#include <dolphin/dolphin.h>
 
 enum SubmenuIndex {
     SubmenuIndexEmulate,
     SubmenuIndexEditUid,
+    SubmenuIndexDetectReader,
+    SubmenuIndexWrite,
+    SubmenuIndexUpdate,
     SubmenuIndexRename,
     SubmenuIndexDelete,
     SubmenuIndexInfo,
@@ -41,6 +45,28 @@ void nfc_scene_saved_menu_on_enter(void* context) {
         submenu_add_item(
             submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_saved_menu_submenu_callback, nfc);
     }
+    if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) {
+        if(!mf_classic_is_card_read(&nfc->dev->dev_data.mf_classic_data)) {
+            submenu_add_item(
+                submenu,
+                "Detect reader",
+                SubmenuIndexDetectReader,
+                nfc_scene_saved_menu_submenu_callback,
+                nfc);
+        }
+        submenu_add_item(
+            submenu,
+            "Write To Initial Card",
+            SubmenuIndexWrite,
+            nfc_scene_saved_menu_submenu_callback,
+            nfc);
+        submenu_add_item(
+            submenu,
+            "Update From Initial Card",
+            SubmenuIndexUpdate,
+            nfc_scene_saved_menu_submenu_callback,
+            nfc);
+    }
     submenu_add_item(
         submenu, "Info", SubmenuIndexInfo, nfc_scene_saved_menu_submenu_callback, nfc);
     if(nfc->dev->shadow_file_exist) {
@@ -76,6 +102,16 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
             } else {
                 scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid);
             }
+            DOLPHIN_DEED(DolphinDeedNfcEmulate);
+            consumed = true;
+        } else if(event.event == SubmenuIndexDetectReader) {
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneDetectReader);
+            consumed = true;
+        } else if(event.event == SubmenuIndexWrite) {
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrite);
+            consumed = true;
+        } else if(event.event == SubmenuIndexUpdate) {
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicUpdate);
             consumed = true;
         } else if(event.event == SubmenuIndexRename) {
             scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName);

+ 0 - 2
applications/main/nfc/scenes/nfc_scene_set_uid.c

@@ -1,5 +1,4 @@
 #include "../nfc_i.h"
-#include <dolphin/dolphin.h>
 
 void nfc_scene_set_uid_byte_input_callback(void* context) {
     Nfc* nfc = context;
@@ -30,7 +29,6 @@ bool nfc_scene_set_uid_on_event(void* context, SceneManagerEvent event) {
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == NfcCustomEventByteInputDone) {
-            DOLPHIN_DEED(DolphinDeedNfcAddSave);
             if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu)) {
                 nfc->dev->dev_data.nfc_data = nfc->dev_edit_data;
                 if(nfc_device_save(nfc->dev, nfc->dev->dev_name)) {

+ 4 - 0
applications/main/nfc/scenes/nfc_scene_start.c

@@ -1,4 +1,5 @@
 #include "../nfc_i.h"
+#include <dolphin/dolphin.h>
 
 enum SubmenuIndex {
     SubmenuIndexRead,
@@ -47,11 +48,14 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) {
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SubmenuIndexRead) {
             scene_manager_next_scene(nfc->scene_manager, NfcSceneRead);
+            DOLPHIN_DEED(DolphinDeedNfcRead);
             consumed = true;
         } else if(event.event == SubmenuIndexDetectReader) {
             bool sd_exist = storage_sd_status(nfc->dev->storage) == FSE_OK;
             if(sd_exist) {
+                nfc_device_data_clear(&nfc->dev->dev_data);
                 scene_manager_next_scene(nfc->scene_manager, NfcSceneDetectReader);
+                DOLPHIN_DEED(DolphinDeedNfcDetectReader);
             } else {
                 scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound);
             }

+ 37 - 1
applications/main/nfc/views/detect_reader.c

@@ -1,7 +1,9 @@
 #include "detect_reader.h"
-
+#include <assets_icons.h>
 #include <gui/elements.h>
 
+#define DETECT_READER_UID_MAX_LEN (10)
+
 struct DetectReader {
     View* view;
     DetectReaderDoneCallback callback;
@@ -12,6 +14,7 @@ typedef struct {
     uint16_t nonces;
     uint16_t nonces_max;
     DetectReaderState state;
+    FuriString* uid_str;
 } DetectReaderViewModel;
 
 static void detect_reader_draw_callback(Canvas* canvas, void* model) {
@@ -23,6 +26,10 @@ static void detect_reader_draw_callback(Canvas* canvas, void* model) {
     if(m->state == DetectReaderStateStart) {
         snprintf(text, sizeof(text), "Touch the reader");
         canvas_draw_icon(canvas, 21, 13, &I_Move_flipper_26x39);
+        if(furi_string_size(m->uid_str)) {
+            elements_multiline_text_aligned(
+                canvas, 64, 64, AlignCenter, AlignBottom, furi_string_get_cstr(m->uid_str));
+        }
     } else if(m->state == DetectReaderStateReaderDetected) {
         snprintf(text, sizeof(text), "Move the Flipper away");
         canvas_draw_icon(canvas, 24, 25, &I_Release_arrow_18x15);
@@ -86,12 +93,24 @@ DetectReader* detect_reader_alloc() {
     view_set_input_callback(detect_reader->view, detect_reader_input_callback);
     view_set_context(detect_reader->view, detect_reader);
 
+    with_view_model(
+        detect_reader->view,
+        DetectReaderViewModel * model,
+        { model->uid_str = furi_string_alloc(); },
+        false);
+
     return detect_reader;
 }
 
 void detect_reader_free(DetectReader* detect_reader) {
     furi_assert(detect_reader);
 
+    with_view_model(
+        detect_reader->view,
+        DetectReaderViewModel * model,
+        { furi_string_free(model->uid_str); },
+        false);
+
     view_free(detect_reader->view);
     free(detect_reader);
 }
@@ -106,6 +125,7 @@ void detect_reader_reset(DetectReader* detect_reader) {
             model->nonces = 0;
             model->nonces_max = 0;
             model->state = DetectReaderStateStart;
+            furi_string_reset(model->uid_str);
         },
         false);
 }
@@ -152,3 +172,19 @@ void detect_reader_set_state(DetectReader* detect_reader, DetectReaderState stat
     with_view_model(
         detect_reader->view, DetectReaderViewModel * model, { model->state = state; }, true);
 }
+
+void detect_reader_set_uid(DetectReader* detect_reader, uint8_t* uid, uint8_t uid_len) {
+    furi_assert(detect_reader);
+    furi_assert(uid);
+    furi_assert(uid_len < DETECT_READER_UID_MAX_LEN);
+    with_view_model(
+        detect_reader->view,
+        DetectReaderViewModel * model,
+        {
+            furi_string_set_str(model->uid_str, "UID:");
+            for(size_t i = 0; i < uid_len; i++) {
+                furi_string_cat_printf(model->uid_str, " %02X", uid[i]);
+            }
+        },
+        true);
+}

+ 2 - 0
applications/main/nfc/views/detect_reader.h

@@ -32,3 +32,5 @@ void detect_reader_set_nonces_max(DetectReader* detect_reader, uint16_t nonces_m
 void detect_reader_set_nonces_collected(DetectReader* detect_reader, uint16_t nonces_collected);
 
 void detect_reader_set_state(DetectReader* detect_reader, DetectReaderState state);
+
+void detect_reader_set_uid(DetectReader* detect_reader, uint8_t* uid, uint8_t uid_len);

+ 0 - 2
applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c

@@ -1,6 +1,5 @@
 #include "../subghz_i.h"
 #include "../views/subghz_frequency_analyzer.h"
-#include <dolphin/dolphin.h>
 
 void subghz_scene_frequency_analyzer_callback(SubGhzCustomEvent event, void* context) {
     furi_assert(context);
@@ -10,7 +9,6 @@ void subghz_scene_frequency_analyzer_callback(SubGhzCustomEvent event, void* con
 
 void subghz_scene_frequency_analyzer_on_enter(void* context) {
     SubGhz* subghz = context;
-    DOLPHIN_DEED(DolphinDeedSubGhzFrequencyAnalyzer);
     subghz_frequency_analyzer_set_callback(
         subghz->subghz_frequency_analyzer, subghz_scene_frequency_analyzer_callback, subghz);
     view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdFrequencyAnalyzer);

+ 50 - 7
applications/main/subghz/scenes/subghz_scene_read_raw.c

@@ -6,6 +6,7 @@
 
 #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;
@@ -72,24 +73,33 @@ void subghz_scene_read_raw_on_enter(void* context) {
 
     switch(subghz->txrx->rx_key_state) {
     case SubGhzRxKeyStateBack:
-        subghz_read_raw_set_status(subghz->subghz_read_raw, SubGhzReadRAWStatusIDLE, "");
+        subghz_read_raw_set_status(
+            subghz->subghz_read_raw, SubGhzReadRAWStatusIDLE, "", subghz->txrx->raw_threshold_rssi);
         break;
     case SubGhzRxKeyStateRAWLoad:
         path_extract_filename(subghz->file_path, file_name, true);
         subghz_read_raw_set_status(
             subghz->subghz_read_raw,
             SubGhzReadRAWStatusLoadKeyTX,
-            furi_string_get_cstr(file_name));
+            furi_string_get_cstr(file_name),
+            subghz->txrx->raw_threshold_rssi);
         subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
         break;
     case SubGhzRxKeyStateRAWSave:
         path_extract_filename(subghz->file_path, file_name, true);
         subghz_read_raw_set_status(
-            subghz->subghz_read_raw, SubGhzReadRAWStatusSaveKey, furi_string_get_cstr(file_name));
+            subghz->subghz_read_raw,
+            SubGhzReadRAWStatusSaveKey,
+            furi_string_get_cstr(file_name),
+            subghz->txrx->raw_threshold_rssi);
         subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
         break;
     default:
-        subghz_read_raw_set_status(subghz->subghz_read_raw, SubGhzReadRAWStatusStart, "");
+        subghz_read_raw_set_status(
+            subghz->subghz_read_raw,
+            SubGhzReadRAWStatusStart,
+            "",
+            subghz->txrx->raw_threshold_rssi);
         subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
         break;
     }
@@ -213,7 +223,12 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
                         subghz->txrx->rx_key_state = SubGhzRxKeyStateBack;
                         scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx);
                     } else {
-                        DOLPHIN_DEED(DolphinDeedSubGhzSend);
+                        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(
@@ -273,7 +288,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
             if(subghz->txrx->rx_key_state != SubGhzRxKeyStateIDLE) {
                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneNeedSaving);
             } else {
-                //subghz_get_preset_name(subghz, subghz->error_str);
+                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,
@@ -319,7 +334,35 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
                 subghz->subghz_read_raw,
                 subghz_protocol_raw_get_sample_write(
                     (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result));
-            subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, furi_hal_subghz_get_rssi());
+
+            float rssi = furi_hal_subghz_get_rssi();
+
+            if(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);
+                } 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);
+                }
+            }
+
             break;
         case SubGhzNotificationStateTx:
             notification_message(subghz->notifications, &sequence_blink_magenta_10);

+ 2 - 0
applications/main/subghz/scenes/subghz_scene_receiver.c

@@ -1,5 +1,6 @@
 #include "../subghz_i.h"
 #include "../views/receiver.h"
+#include <dolphin/dolphin.h>
 
 static const NotificationSequence subghs_sequence_rx = {
     &message_green_255,
@@ -181,6 +182,7 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) {
             subghz->txrx->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:

+ 52 - 0
applications/main/subghz/scenes/subghz_scene_receiver_config.c

@@ -1,10 +1,41 @@
 #include "../subghz_i.h"
+#include <lib/toolbox/value_index.h>
 
 enum SubGhzSettingIndex {
     SubGhzSettingIndexFrequency,
     SubGhzSettingIndexHopping,
     SubGhzSettingIndexModulation,
     SubGhzSettingIndexLock,
+    SubGhzSettingIndexRAWThesholdRSSI,
+};
+
+#define RAW_THRESHOLD_RSSI_COUNT 11
+const char* const raw_theshold_rssi_text[RAW_THRESHOLD_RSSI_COUNT] = {
+    "-----",
+    "-85.0",
+    "-80.0",
+    "-75.0",
+    "-70.0",
+    "-65.0",
+    "-60.0",
+    "-55.0",
+    "-50.0",
+    "-45.0",
+    "-40.0",
+
+};
+const float raw_theshold_rssi_value[RAW_THRESHOLD_RSSI_COUNT] = {
+    -90.0f,
+    -85.0f,
+    -80.0f,
+    -75.0f,
+    -70.0f,
+    -65.0f,
+    -60.0f,
+    -55.0f,
+    -50.0f,
+    -45.0f,
+    -40.0f,
 };
 
 #define HOPPING_COUNT 2
@@ -136,6 +167,14 @@ static void subghz_scene_receiver_config_set_hopping_running(VariableItem* item)
     subghz->txrx->hopper_state = hopping_value[index];
 }
 
+static void subghz_scene_receiver_config_set_raw_threshold_rssi(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, raw_theshold_rssi_text[index]);
+    subghz->txrx->raw_threshold_rssi = raw_theshold_rssi_value[index];
+}
+
 static void subghz_scene_receiver_config_var_list_enter_callback(void* context, uint32_t index) {
     furi_assert(context);
     SubGhz* subghz = context;
@@ -204,6 +243,19 @@ void subghz_scene_receiver_config_on_enter(void* context) {
             subghz_scene_receiver_config_var_list_enter_callback,
             subghz);
     }
+    if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) ==
+       SubGhzCustomEventManagerSet) {
+        item = variable_item_list_add(
+            subghz->variable_item_list,
+            "RSSI Threshold:",
+            RAW_THRESHOLD_RSSI_COUNT,
+            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);
+        variable_item_set_current_value_index(item, value_index);
+        variable_item_set_current_value_text(item, raw_theshold_rssi_text[value_index]);
+    }
     view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList);
 }
 

+ 0 - 2
applications/main/subghz/scenes/subghz_scene_receiver_info.c

@@ -1,6 +1,5 @@
 #include "../subghz_i.h"
 #include "../helpers/subghz_custom_event.h"
-#include <dolphin/dolphin.h>
 
 void subghz_scene_receiver_info_callback(GuiButtonType result, InputType type, void* context) {
     furi_assert(context);
@@ -45,7 +44,6 @@ static bool subghz_scene_receiver_info_update_parser(void* context) {
 void subghz_scene_receiver_info_on_enter(void* context) {
     SubGhz* subghz = context;
 
-    DOLPHIN_DEED(DolphinDeedSubGhzReceiverInfo);
     if(subghz_scene_receiver_info_update_parser(subghz)) {
         FuriString* frequency_str;
         FuriString* modulation_str;

+ 12 - 0
applications/main/subghz/scenes/subghz_scene_save_name.c

@@ -4,6 +4,7 @@
 #include "../helpers/subghz_custom_event.h"
 #include <lib/subghz/protocols/raw.h>
 #include <gui/modules/validators.h>
+#include <dolphin/dolphin.h>
 
 #define MAX_TEXT_INPUT_LEN 22
 
@@ -131,6 +132,17 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) {
                 }
 
                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveSuccess);
+                if(scene_manager_has_previous_scene(subghz->scene_manager, SubGhzSceneSavedMenu)) {
+                    // Nothing, do not count editing as saving
+                } else if(scene_manager_has_previous_scene(
+                              subghz->scene_manager, SubGhzSceneMoreRAW)) {
+                    // Ditto, for RAW signals
+                } else if(scene_manager_has_previous_scene(
+                              subghz->scene_manager, SubGhzSceneSetType)) {
+                    DOLPHIN_DEED(DolphinDeedSubGhzAddManually);
+                } else {
+                    DOLPHIN_DEED(DolphinDeedSubGhzSave);
+                }
                 return true;
             } else {
                 furi_string_set(subghz->error_str, "No name file");

+ 0 - 3
applications/main/subghz/scenes/subghz_scene_save_success.c

@@ -1,7 +1,5 @@
 #include "../subghz_i.h"
 #include "../helpers/subghz_custom_event.h"
-#include <dolphin/helpers/dolphin_deed.h>
-#include <dolphin/dolphin.h>
 
 void subghz_scene_save_success_popup_callback(void* context) {
     SubGhz* subghz = context;
@@ -10,7 +8,6 @@ void subghz_scene_save_success_popup_callback(void* context) {
 
 void subghz_scene_save_success_on_enter(void* context) {
     SubGhz* subghz = context;
-    DOLPHIN_DEED(DolphinDeedSubGhzSave);
 
     // Setup view
     Popup* popup = subghz->popup;

+ 0 - 2
applications/main/subghz/scenes/subghz_scene_set_type.c

@@ -3,7 +3,6 @@
 #include <lib/subghz/protocols/secplus_v1.h>
 #include <lib/subghz/protocols/secplus_v2.h>
 #include <lib/subghz/blocks/math.h>
-#include <dolphin/dolphin.h>
 #include <flipper_format/flipper_format_i.h>
 #include <lib/toolbox/stream/stream.h>
 #include <lib/subghz/protocols/protocol_items.h>
@@ -381,7 +380,6 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) {
 
         if(generated_protocol) {
             subghz_file_name_clear(subghz);
-            DOLPHIN_DEED(DolphinDeedSubGhzAddManually);
             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName);
             return true;
         }

+ 2 - 0
applications/main/subghz/scenes/subghz_scene_start.c

@@ -1,4 +1,5 @@
 #include "../subghz_i.h"
+#include <dolphin/dolphin.h>
 
 enum SubmenuIndex {
     SubmenuIndexRead = 10,
@@ -84,6 +85,7 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) {
             scene_manager_set_scene_state(
                 subghz->scene_manager, SubGhzSceneStart, SubmenuIndexFrequencyAnalyzer);
             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneFrequencyAnalyzer);
+            DOLPHIN_DEED(DolphinDeedSubGhzFrequencyAnalyzer);
             return true;
         } else if(event.event == SubmenuIndexTest) {
             scene_manager_set_scene_state(

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

@@ -50,7 +50,6 @@ bool subghz_scene_transmitter_update_data_show(void* context) {
 
 void subghz_scene_transmitter_on_enter(void* context) {
     SubGhz* subghz = context;
-    DOLPHIN_DEED(DolphinDeedSubGhzSend);
     if(!subghz_scene_transmitter_update_data_show(subghz)) {
         view_dispatcher_send_custom_event(
             subghz->view_dispatcher, SubGhzCustomEventViewTransmitterError);
@@ -78,6 +77,7 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) {
                 } else {
                     subghz->state_notifications = SubGhzNotificationStateTx;
                     subghz_scene_transmitter_update_data_show(subghz);
+                    DOLPHIN_DEED(DolphinDeedSubGhzSend);
                 }
             }
             return true;

+ 1 - 0
applications/main/subghz/subghz.c

@@ -178,6 +178,7 @@ SubGhz* subghz_alloc() {
     subghz->txrx->txrx_state = SubGhzTxRxStateSleep;
     subghz->txrx->hopper_state = SubGhzHopperStateOFF;
     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();

+ 1 - 1
applications/main/subghz/subghz_i.c

@@ -102,8 +102,8 @@ static bool subghz_tx(SubGhz* subghz, uint32_t 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);
-    furi_hal_gpio_write(&gpio_cc1101_g0, true);
     bool ret = furi_hal_subghz_tx();
     subghz->txrx->txrx_state = SubGhzTxRxStateTx;
     return ret;

+ 4 - 0
applications/main/subghz/subghz_i.h

@@ -13,6 +13,7 @@
 #include "views/subghz_test_packet.h"
 
 #include <gui/gui.h>
+#include <assets_icons.h>
 #include <dialogs/dialogs.h>
 #include <gui/scene_manager.h>
 #include <notification/notification_messages.h>
@@ -54,6 +55,9 @@ struct SubGhzTxRx {
     uint8_t hopper_timeout;
     uint8_t hopper_idx_frequency;
     SubGhzRxKeyState rx_key_state;
+
+    float raw_threshold_rssi;
+    uint8_t raw_threshold_rssi_low_count;
 };
 
 typedef struct SubGhzTxRx SubGhzTxRx;

+ 68 - 9
applications/main/subghz/views/subghz_read_raw.c

@@ -23,10 +23,12 @@ typedef struct {
     FuriString* sample_write;
     FuriString* file_name;
     uint8_t* rssi_history;
+    uint8_t rssi_curret;
     bool rssi_history_end;
     uint8_t ind_write;
     uint8_t ind_sin;
     SubGhzReadRAWStatus status;
+    float raw_threshold_rssi;
 } SubGhzReadRAWModel;
 
 void subghz_read_raw_set_callback(
@@ -54,21 +56,27 @@ void subghz_read_raw_add_data_statusbar(
         true);
 }
 
-void subghz_read_raw_add_data_rssi(SubGhzReadRAW* instance, float rssi) {
+void subghz_read_raw_add_data_rssi(SubGhzReadRAW* instance, float rssi, bool trace) {
     furi_assert(instance);
     uint8_t u_rssi = 0;
 
-    if(rssi < -90) {
+    if(rssi < SUBGHZ_RAW_TRESHOLD_MIN) {
         u_rssi = 0;
     } else {
-        u_rssi = (uint8_t)((rssi + 90) / 2.7);
+        u_rssi = (uint8_t)((rssi - SUBGHZ_RAW_TRESHOLD_MIN) / 2.7);
     }
 
     with_view_model(
         instance->view,
         SubGhzReadRAWModel * model,
         {
-            model->rssi_history[model->ind_write++] = u_rssi;
+            model->rssi_curret = u_rssi;
+            if(trace) {
+                model->rssi_history[model->ind_write++] = u_rssi;
+            } else {
+                model->rssi_history[model->ind_write] = u_rssi;
+            }
+
             if(model->ind_write > SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE) {
                 model->rssi_history_end = true;
                 model->ind_write = 0;
@@ -187,24 +195,53 @@ void subghz_read_raw_draw_scale(Canvas* canvas, SubGhzReadRAWModel* model) {
 void subghz_read_raw_draw_rssi(Canvas* canvas, SubGhzReadRAWModel* model) {
     int ind = 0;
     int base = 0;
+    uint8_t width = 2;
     if(model->rssi_history_end == false) {
         for(int i = model->ind_write; i >= 0; i--) {
             canvas_draw_line(canvas, i, 47, i, 47 - model->rssi_history[i]);
         }
+        canvas_draw_line(
+            canvas, model->ind_write + 1, 47, model->ind_write + 1, 47 - model->rssi_curret);
         if(model->ind_write > 3) {
-            canvas_draw_line(canvas, model->ind_write, 47, model->ind_write, 13);
+            canvas_draw_line(
+                canvas, model->ind_write - 1, 47, model->ind_write - 1, 47 - model->rssi_curret);
+
+            for(uint8_t i = 13; i < 47; i += width * 2) {
+                canvas_draw_line(canvas, model->ind_write, i, model->ind_write, i + width);
+            }
             canvas_draw_line(canvas, model->ind_write - 2, 12, model->ind_write + 2, 12);
             canvas_draw_line(canvas, model->ind_write - 1, 13, model->ind_write + 1, 13);
         }
     } else {
+        int i = 0;
         base = SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE - model->ind_write;
-        for(int i = SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE; i >= 0; i--) {
+        for(i = SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE; i > 0; i--) {
             ind = i - base;
             if(ind < 0) ind += SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE;
             canvas_draw_line(canvas, i, 47, i, 47 - model->rssi_history[ind]);
         }
+
         canvas_draw_line(
-            canvas, SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE, 47, SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE, 13);
+            canvas,
+            SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE - 1,
+            47,
+            SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE - 1,
+            47 - model->rssi_curret);
+        canvas_draw_line(
+            canvas,
+            SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE + 1,
+            47,
+            SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE + 1,
+            47 - model->rssi_curret);
+
+        for(uint8_t i = 13; i < 47; i += width * 2) {
+            canvas_draw_line(
+                canvas,
+                SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE,
+                i,
+                SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE,
+                i + width);
+        }
         canvas_draw_line(
             canvas,
             SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE - 2,
@@ -220,6 +257,24 @@ void subghz_read_raw_draw_rssi(Canvas* canvas, SubGhzReadRAWModel* model) {
     }
 }
 
+void subghz_read_raw_draw_threshold_rssi(Canvas* canvas, SubGhzReadRAWModel* model) {
+    uint8_t x = 118;
+    uint8_t y = 48;
+
+    if(model->raw_threshold_rssi > SUBGHZ_RAW_TRESHOLD_MIN) {
+        uint8_t x = 118;
+        y -= (uint8_t)((model->raw_threshold_rssi - SUBGHZ_RAW_TRESHOLD_MIN) / 2.7);
+
+        uint8_t width = 3;
+        for(uint8_t i = 0; i < x; i += width * 2) {
+            canvas_draw_line(canvas, i, y, i + width, y);
+        }
+    }
+    canvas_draw_line(canvas, x, y - 2, x, y + 2);
+    canvas_draw_line(canvas, x - 1, y - 1, x - 1, y + 1);
+    canvas_draw_dot(canvas, x - 2, y);
+}
+
 void subghz_read_raw_draw(Canvas* canvas, SubGhzReadRAWModel* model) {
     uint8_t graphics_mode = 1;
     canvas_set_color(canvas, ColorBlack);
@@ -278,8 +333,9 @@ void subghz_read_raw_draw(Canvas* canvas, SubGhzReadRAWModel* model) {
     } else {
         subghz_read_raw_draw_rssi(canvas, model);
         subghz_read_raw_draw_scale(canvas, model);
+        subghz_read_raw_draw_threshold_rssi(canvas, model);
         canvas_set_font_direction(canvas, CanvasDirectionBottomToTop);
-        canvas_draw_str(canvas, 126, 40, "RSSI");
+        canvas_draw_str(canvas, 128, 40, "RSSI");
         canvas_set_font_direction(canvas, CanvasDirectionLeftToRight);
     }
 }
@@ -433,7 +489,8 @@ bool subghz_read_raw_input(InputEvent* event, void* context) {
 void subghz_read_raw_set_status(
     SubGhzReadRAW* instance,
     SubGhzReadRAWStatus status,
-    const char* file_name) {
+    const char* file_name,
+    float raw_threshold_rssi) {
     furi_assert(instance);
 
     switch(status) {
@@ -447,6 +504,7 @@ void subghz_read_raw_set_status(
                 model->ind_write = 0;
                 furi_string_reset(model->file_name);
                 furi_string_set(model->sample_write, "0 spl.");
+                model->raw_threshold_rssi = raw_threshold_rssi;
             },
             true);
         break;
@@ -536,6 +594,7 @@ SubGhzReadRAW* subghz_read_raw_alloc() {
             model->sample_write = furi_string_alloc();
             model->file_name = furi_string_alloc();
             model->rssi_history = malloc(SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE * sizeof(uint8_t));
+            model->raw_threshold_rssi = -127.0f;
         },
         true);
 

+ 5 - 2
applications/main/subghz/views/subghz_read_raw.h

@@ -3,6 +3,8 @@
 #include <gui/view.h>
 #include "../helpers/subghz_custom_event.h"
 
+#define SUBGHZ_RAW_TRESHOLD_MIN -90.0f
+
 typedef struct SubGhzReadRAW SubGhzReadRAW;
 
 typedef void (*SubGhzReadRAWCallback)(SubGhzCustomEvent event, void* context);
@@ -40,11 +42,12 @@ void subghz_read_raw_stop_send(SubGhzReadRAW* instance);
 
 void subghz_read_raw_update_sin(SubGhzReadRAW* instance);
 
-void subghz_read_raw_add_data_rssi(SubGhzReadRAW* instance, float rssi);
+void subghz_read_raw_add_data_rssi(SubGhzReadRAW* instance, float rssi, bool trace);
 
 void subghz_read_raw_set_status(
     SubGhzReadRAW* instance,
     SubGhzReadRAWStatus status,
-    const char* file_name);
+    const char* file_name,
+    float raw_threshold_rssi);
 
 View* subghz_read_raw_get_view(SubGhzReadRAW* subghz_static);

+ 1 - 0
applications/main/u2f/u2f_app_i.h

@@ -4,6 +4,7 @@
 #include "scenes/u2f_scene.h"
 
 #include <gui/gui.h>
+#include <assets_icons.h>
 #include <gui/view_dispatcher.h>
 #include <gui/scene_manager.h>
 #include <gui/modules/submenu.h>

+ 1 - 0
applications/main/u2f/views/u2f_view.c

@@ -1,5 +1,6 @@
 #include "u2f_view.h"
 #include <gui/elements.h>
+#include <assets_icons.h>
 
 struct U2fView {
     View* view;

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