MX 2 лет назад
Родитель
Сommit
718aaa39da
37 измененных файлов с 2414 добавлено и 0 удалено
  1. 1 0
      ReadMe.md
  2. 144 0
      non_catalog_apps/uhf_rfid/README.md
  3. 17 0
      non_catalog_apps/uhf_rfid/application.fam
  4. BIN
      non_catalog_apps/uhf_rfid/assets/img/uhf_demo_app.jpg
  5. BIN
      non_catalog_apps/uhf_rfid/icons/DolphinMafia_115x62.png
  6. BIN
      non_catalog_apps/uhf_rfid/icons/DolphinNice_96x59.png
  7. BIN
      non_catalog_apps/uhf_rfid/icons/Nfc_10px.png
  8. BIN
      non_catalog_apps/uhf_rfid/icons/RFIDDolphinReceive_97x61.png
  9. BIN
      non_catalog_apps/uhf_rfid/icons/RFIDDolphinSend_97x61.png
  10. 30 0
      non_catalog_apps/uhf_rfid/scenes/uhf_scene.c
  11. 29 0
      non_catalog_apps/uhf_rfid/scenes/uhf_scene.h
  12. 62 0
      non_catalog_apps/uhf_rfid/scenes/uhf_scene_card_menu.c
  13. 17 0
      non_catalog_apps/uhf_rfid/scenes/uhf_scene_config.h
  14. 50 0
      non_catalog_apps/uhf_rfid/scenes/uhf_scene_delete.c
  15. 40 0
      non_catalog_apps/uhf_rfid/scenes/uhf_scene_delete_success.c
  16. 133 0
      non_catalog_apps/uhf_rfid/scenes/uhf_scene_device_info.c
  17. 23 0
      non_catalog_apps/uhf_rfid/scenes/uhf_scene_file_select.c
  18. 49 0
      non_catalog_apps/uhf_rfid/scenes/uhf_scene_read_tag.c
  19. 94 0
      non_catalog_apps/uhf_rfid/scenes/uhf_scene_read_tag_success.c
  20. 74 0
      non_catalog_apps/uhf_rfid/scenes/uhf_scene_save_name.c
  21. 47 0
      non_catalog_apps/uhf_rfid/scenes/uhf_scene_save_success.c
  22. 60 0
      non_catalog_apps/uhf_rfid/scenes/uhf_scene_saved_menu.c
  23. 56 0
      non_catalog_apps/uhf_rfid/scenes/uhf_scene_start.c
  24. 155 0
      non_catalog_apps/uhf_rfid/scenes/uhf_scene_verify.c
  25. BIN
      non_catalog_apps/uhf_rfid/uhf_10px.png
  26. 203 0
      non_catalog_apps/uhf_rfid/uhf_app.c
  27. 3 0
      non_catalog_apps/uhf_rfid/uhf_app.h
  28. 104 0
      non_catalog_apps/uhf_rfid/uhf_app_i.h
  29. 0 0
      non_catalog_apps/uhf_rfid/uhf_cmd.c
  30. 114 0
      non_catalog_apps/uhf_rfid/uhf_cmd.h
  31. 148 0
      non_catalog_apps/uhf_rfid/uhf_data.c
  32. 64 0
      non_catalog_apps/uhf_rfid/uhf_data.h
  33. 2 0
      non_catalog_apps/uhf_rfid/uhf_data_i.h
  34. 325 0
      non_catalog_apps/uhf_rfid/uhf_device.c
  35. 65 0
      non_catalog_apps/uhf_rfid/uhf_device.h
  36. 256 0
      non_catalog_apps/uhf_rfid/uhf_worker.c
  37. 49 0
      non_catalog_apps/uhf_rfid/uhf_worker.h

+ 1 - 0
ReadMe.md

@@ -174,6 +174,7 @@ Games:
 ## RFID
 ## RFID
 - [RFID Beacon (by nmrr)](https://github.com/nmrr/flipperzero-rfidbeacon) - `A letter/number Morse beacon on 125 kHz`
 - [RFID Beacon (by nmrr)](https://github.com/nmrr/flipperzero-rfidbeacon) - `A letter/number Morse beacon on 125 kHz`
 - [EM4100 universal keys generator (for RFID Fuzzer) (by Milk-Cool)](https://github.com/Milk-Cool/fz-em4100-generator)
 - [EM4100 universal keys generator (for RFID Fuzzer) (by Milk-Cool)](https://github.com/Milk-Cool/fz-em4100-generator)
+- [YRM100 UHF RFID (by frux-c)](https://github.com/frux-c/uhf_rfid) -> WIP -> (+Added icon by @xMasterX)
 
 
 ## Sub-GHz
 ## Sub-GHz
 - [Enhanced Sub-GHz Chat (by twisted-pear)](https://github.com/twisted-pear/esubghz_chat)
 - [Enhanced Sub-GHz Chat (by twisted-pear)](https://github.com/twisted-pear/esubghz_chat)

+ 144 - 0
non_catalog_apps/uhf_rfid/README.md

@@ -0,0 +1,144 @@
+# [UHF]RFID App for FlipperZero
+
+![FlipperZero](assets/img/uhf_demo_app.jpg)
+
+## Overview
+
+This repository contains a UHF RFID application developed for FlipperZero, a versatile multi-tool device. The app leverages the YRM100 module to enable UHF RFID functionality.
+
+## Features
+
+- [x] Read Single UHF RFID tag.
+- [x] View saved UHF RFID tag.
+- [ ] Write Single UHF RFID tag. __(in progress)__
+- [ ] Change Module setting parameters.
+- [ ] Easy-to-use interface on FlipperZero's display.
+    - Extras
+        - [ ] Read multiple tags at once
+        - [ ] View multiple on a list view
+
+## Requirements
+
+To run this application on FlipperZero, you will need:
+
+- FlipperZero device (purchase from [Flipper Devices](https://www.flipperdevices.com))
+- YRM100 UHF RFID module (purchase from [Ali-Express](https://www.aliexpress.com/item/1005005296512846.html))
+
+## Setup and Installation
+
+1. Ensure you have set up your FlipperZero device with the YRM100 module properly. You can also read more about how to setup the module from the [Md5Stack Docs page](http://docs.m5stack.com/en/unit/uhf_rfid).
+   ![wiring diagram](https://static-cdn.m5stack.com/resource/docs/products/unit/uhf_rfid/uhf_rfid_sch_01.webp)
+2. Clone this repository to the `applications_user` folder of your flipper firmware of your choice
+3. If you have VSCode setup with your flipper firmware.
+   - ### Windows
+     1. Press `Ctrl+Shift+B` on vscode while in the uhf_app folder
+     2. Select the `Launch App on Flipper` option. And watch as the app launches on your flipper
+     - If you don't have vscode setup you can use the cli command `./fbt COMPACT=1 DEBUG=0 launch APPSRC=applications_user\uhf_rfid`
+   - ### MacOS
+     ... tbd
+
+## Usage
+
+1. Power on your FlipperZero device.
+2. Connect the uhf module to the flipper via gpio.
+3. Navigate to the UHF RFID app on FlipperZero's menu.
+4. Currently Reading the EPC tag is the only usable option
+... will further update this page as it development goes
+
+## Contributions
+
+As this app is still in the development stage, I welcome contributions to this project. If you find any issues or want to enhance the application, feel free to create a pull request.
+
+<!-- ## License
+
+This project is licensed under the [MIT License](link_to_license_file). -->
+
+## Future Plans
+- Code cleanup
+- Build a framework around the chip communication commands
+- Build a proper tag class
+```c
+// Ideal concept
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+typedef struct {
+    int uart_fd; // UART file descriptor or other identifier
+} YRM100_RFID;
+
+void sendCommand(YRM100_RFID *rfid, const uint8_t *command, size_t length) {
+    // Implementation to send the command through UART
+    // Write the command to the UART interface using rfid->uart_fd
+}
+
+// Configuration functions:
+
+void setCommunicationBaudRate(YRM100_RFID *rfid) {
+    uint8_t command[] = {0xBB, 0x00, 0x11, 0x00, 0x02, 0x00, 0xC0, 0xD3, 0x7E};
+    sendCommand(rfid, command, sizeof(command));
+}
+
+void setWorkingArea(YRM100_RFID *rfid, uint8_t area) {
+    uint8_t command[] = {0xBB, 0x00, 0x07, 0x00, 0x01, area, 0x09, 0x7E}; 
+    sendCommand(rfid, command, sizeof(command));
+}
+
+// other method etc ... 
+```
+
+```c
+// Ideal concept
+#include <stdint.h>
+#include <stdlib.h>
+
+typedef struct {
+    uint8_t *killPassword;
+    uint8_t *accessPassword;
+    size_t size;
+} ReservedMemory;
+
+typedef struct {
+    uint8_t *header;
+    uint8_t *filter;
+    uint8_t *partition;
+    uint8_t *companyPrefix;
+    uint8_t *itemReference;
+    size_t size;
+} EPCMemory;
+
+typedef struct {
+    uint8_t *tid;
+    size_t size;
+} TIDMemory;
+
+typedef struct {
+    uint8_t *userMemory;
+    size_t size;
+} UserMemory;
+
+typedef struct {
+    ReservedMemory reserved;
+    EPCMemory epc;
+    TIDMemory tid;
+    UserMemory user;
+} ISO18000_6C_Tag;
+```
+
+## Disclaimer
+
+- This application is provided as-is and may contain bugs or issues.
+- Use it at your own risk.
+- I am not responsible for any damage or loss caused by the usage of this app.
+
+## Extra Resources
+
+- [MagicRF M100&QM100_Firmware_manual_en.pdf](assets/res/MagicRF_M100&QM100_Firmware_manual_en.pdf)
+
+## Contact
+
+For any inquiries or support, you can reach out to us at :
+
+- Personal Email : [frux.infoc@gmail.com](mailto:frux.infoc@gmail.com)
+- Discord Server: [Flipper Zero Tutorial-Unoffical by @jamisonderek](https://discord.gg/REunuAnTX9)
+- Discord User: [frux.c]()

+ 17 - 0
non_catalog_apps/uhf_rfid/application.fam

@@ -0,0 +1,17 @@
+App(
+    appid="uhf_rfid",
+    name="[YRM100] UHF RFID",
+    apptype=FlipperAppType.EXTERNAL,
+    targets=["f7"],
+    entry_point="uhf_app_main",
+    requires=[
+        "storage",
+        "gui",
+    ],
+    stack_size=4 * 1024,
+    order=30,
+    fap_icon="uhf_10px.png",
+    fap_category="RFID",
+    fap_icon_assets="icons",
+    fap_icon_assets_symbol="uhf_rfid",
+)

BIN
non_catalog_apps/uhf_rfid/assets/img/uhf_demo_app.jpg


BIN
non_catalog_apps/uhf_rfid/icons/DolphinMafia_115x62.png


BIN
non_catalog_apps/uhf_rfid/icons/DolphinNice_96x59.png


BIN
non_catalog_apps/uhf_rfid/icons/Nfc_10px.png


BIN
non_catalog_apps/uhf_rfid/icons/RFIDDolphinReceive_97x61.png


BIN
non_catalog_apps/uhf_rfid/icons/RFIDDolphinSend_97x61.png


+ 30 - 0
non_catalog_apps/uhf_rfid/scenes/uhf_scene.c

@@ -0,0 +1,30 @@
+#include "uhf_scene.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const uhf_on_enter_handlers[])(void*) = {
+#include "uhf_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_event handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
+bool (*const uhf_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "uhf_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
+void (*const uhf_on_exit_handlers[])(void* context) = {
+#include "uhf_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers uhf_scene_handlers = {
+    .on_enter_handlers = uhf_on_enter_handlers,
+    .on_event_handlers = uhf_on_event_handlers,
+    .on_exit_handlers = uhf_on_exit_handlers,
+    .scene_num = UHFSceneNum,
+};

+ 29 - 0
non_catalog_apps/uhf_rfid/scenes/uhf_scene.h

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

+ 62 - 0
non_catalog_apps/uhf_rfid/scenes/uhf_scene_card_menu.c

@@ -0,0 +1,62 @@
+#include "../uhf_app_i.h"
+
+enum SubmenuIndex {
+    SubmenuIndexSave,
+    SubmenuIndexChangeKey,
+};
+
+void uhf_scene_card_menu_submenu_callback(void* ctx, uint32_t index) {
+    UHFApp* uhf_app = ctx;
+    view_dispatcher_send_custom_event(uhf_app->view_dispatcher, index);
+}
+
+void uhf_scene_card_menu_on_enter(void* ctx) {
+    UHFApp* uhf_app = ctx;
+
+    Submenu* submenu = uhf_app->submenu;
+
+    submenu_add_item(
+        submenu, "Save", SubmenuIndexSave, uhf_scene_card_menu_submenu_callback, uhf_app);
+    submenu_add_item(
+        submenu,
+        "Change Key",
+        SubmenuIndexChangeKey,
+        uhf_scene_card_menu_submenu_callback,
+        uhf_app);
+
+    submenu_set_selected_item(
+        submenu, scene_manager_get_scene_state(uhf_app->scene_manager, UHFSceneCardMenu));
+
+    view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewMenu);
+}
+
+bool uhf_scene_card_menu_on_event(void* ctx, SceneManagerEvent event) {
+    UHFApp* uhf_app = ctx;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SubmenuIndexSave) {
+            scene_manager_set_scene_state(
+                uhf_app->scene_manager, UHFSceneCardMenu, SubmenuIndexSave);
+            scene_manager_next_scene(uhf_app->scene_manager, UHFSceneSaveName);
+            consumed = true;
+        }
+        // else if(event.event == SubmenuIndexChangeKey) {
+        //     scene_manager_set_scene_state(
+        //         picopass->scene_manager, UHFSceneCardMenu, SubmenuIndexChangeKey);
+        //     scene_manager_next_scene(picopass->scene_manager, PicopassSceneKeyMenu);
+        //     consumed = true;
+        // }
+    } else if(event.type == SceneManagerEventTypeBack) {
+        consumed = scene_manager_search_and_switch_to_previous_scene(
+            uhf_app->scene_manager, UHFSceneStart);
+    }
+
+    return consumed;
+}
+
+void uhf_scene_card_menu_on_exit(void* ctx) {
+    UHFApp* uhf_app = ctx;
+
+    submenu_reset(uhf_app->submenu);
+}

+ 17 - 0
non_catalog_apps/uhf_rfid/scenes/uhf_scene_config.h

@@ -0,0 +1,17 @@
+ADD_SCENE(uhf, verify, Verify)
+ADD_SCENE(uhf, start, Start)
+ADD_SCENE(uhf, read_tag, ReadTag)
+ADD_SCENE(uhf, read_tag_success, ReadTagSuccess)
+ADD_SCENE(uhf, card_menu, CardMenu)
+ADD_SCENE(uhf, save_name, SaveName)
+ADD_SCENE(uhf, save_success, SaveSuccess)
+ADD_SCENE(uhf, saved_menu, SavedMenu)
+ADD_SCENE(uhf, file_select, FileSelect)
+ADD_SCENE(uhf, device_info, DeviceInfo)
+ADD_SCENE(uhf, delete, Delete)
+ADD_SCENE(uhf, delete_success, DeleteSuccess)
+// ADD_SCENE(uhf, write_card, WriteCard)
+// ADD_SCENE(uhf, write_card_success, WriteCardSuccess)
+// ADD_SCENE(uhf, read_factory_success, ReadFactorySuccess)
+// ADD_SCENE(uhf, write_key, WriteKey)
+// ADD_SCENE(uhf, key_menu, KeyMenu)

+ 50 - 0
non_catalog_apps/uhf_rfid/scenes/uhf_scene_delete.c

@@ -0,0 +1,50 @@
+#include "../uhf_app_i.h"
+
+void uhf_scene_delete_widget_callback(GuiButtonType result, InputType type, void* context) {
+    UHFApp* uhf_app = context;
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(uhf_app->view_dispatcher, result);
+    }
+}
+
+void uhf_scene_delete_on_enter(void* context) {
+    UHFApp* uhf_app = context;
+
+    // Setup Custom Widget view
+    char temp_str[64];
+    snprintf(temp_str, sizeof(temp_str), "\e#Delete %s?\e#", uhf_app->uhf_device->dev_name);
+    widget_add_text_box_element(
+        uhf_app->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, temp_str, false);
+    widget_add_button_element(
+        uhf_app->widget, GuiButtonTypeLeft, "Back", uhf_scene_delete_widget_callback, uhf_app);
+    widget_add_button_element(
+        uhf_app->widget, GuiButtonTypeRight, "Delete", uhf_scene_delete_widget_callback, uhf_app);
+
+    view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewWidget);
+}
+
+bool uhf_scene_delete_on_event(void* context, SceneManagerEvent event) {
+    UHFApp* uhf_app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == GuiButtonTypeLeft) {
+            return scene_manager_previous_scene(uhf_app->scene_manager);
+        } else if(event.event == GuiButtonTypeRight) {
+            if(uhf_device_delete(uhf_app->uhf_device, true)) {
+                scene_manager_next_scene(uhf_app->scene_manager, UHFSceneDeleteSuccess);
+            } else {
+                scene_manager_search_and_switch_to_previous_scene(
+                    uhf_app->scene_manager, UHFSceneStart);
+            }
+            consumed = true;
+        }
+    }
+    return consumed;
+}
+
+void uhf_scene_delete_on_exit(void* context) {
+    UHFApp* uhf_app = context;
+
+    widget_reset(uhf_app->widget);
+}

+ 40 - 0
non_catalog_apps/uhf_rfid/scenes/uhf_scene_delete_success.c

@@ -0,0 +1,40 @@
+#include "../uhf_app_i.h"
+
+void uhf_scene_delete_success_popup_callback(void* context) {
+    UHFApp* uhf_app = context;
+    view_dispatcher_send_custom_event(uhf_app->view_dispatcher, UHFCustomEventViewExit);
+}
+
+void uhf_scene_delete_success_on_enter(void* context) {
+    UHFApp* uhf_app = context;
+
+    // Setup view
+    Popup* popup = uhf_app->popup;
+    popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62);
+    popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom);
+    popup_set_timeout(popup, 1500);
+    popup_set_context(popup, uhf_app);
+    popup_set_callback(popup, uhf_scene_delete_success_popup_callback);
+    popup_enable_timeout(popup);
+    view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewPopup);
+}
+
+bool uhf_scene_delete_success_on_event(void* context, SceneManagerEvent event) {
+    UHFApp* uhf_app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == UHFCustomEventViewExit) {
+            consumed = scene_manager_search_and_switch_to_previous_scene(
+                uhf_app->scene_manager, UHFSceneStart);
+        }
+    }
+    return consumed;
+}
+
+void uhf_scene_delete_success_on_exit(void* context) {
+    UHFApp* uhf_app = context;
+
+    // Clear view
+    popup_reset(uhf_app->popup);
+}

+ 133 - 0
non_catalog_apps/uhf_rfid/scenes/uhf_scene_device_info.c

@@ -0,0 +1,133 @@
+#include "../uhf_app_i.h"
+#include <dolphin/dolphin.h>
+
+typedef enum { EPC_INFO, TID_INFO, USER_INFO } UHFTagInfo;
+
+static UHFTagInfo current_info;
+
+char* get_current_bank_info_str() {
+    switch(current_info) {
+    case EPC_INFO:
+        return "EPC Bank";
+    case TID_INFO:
+        return "TID Bank";
+    case USER_INFO:
+        return "User Bank";
+    }
+    return "";
+}
+
+char* get_next_bank_info_str() {
+    switch(current_info) {
+    case EPC_INFO:
+        current_info = TID_INFO;
+        return "TID";
+    case TID_INFO:
+        current_info = USER_INFO;
+        return "USER";
+    case USER_INFO:
+        current_info = EPC_INFO;
+        return "EPC";
+    }
+    return "";
+}
+
+void uhf_scene_device_info_widget_callback(GuiButtonType result, InputType type, void* context) {
+    UHFApp* uhf_app = context;
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(uhf_app->view_dispatcher, result);
+    }
+}
+
+void change_view_on_event(UHFApp* uhf_app) {
+    UHFTag* uhf_tag = uhf_app->uhf_device->uhf_tag;
+    FuriString* furi_temp_str;
+    furi_temp_str = furi_string_alloc();
+    char* temp_str;
+    size_t length;
+
+    widget_reset(uhf_app->widget);
+    widget_add_string_element(
+        uhf_app->widget, 64, 5, AlignCenter, AlignCenter, FontPrimary, get_current_bank_info_str());
+
+    switch(current_info) {
+    case EPC_INFO:
+        temp_str = convertToHexString(uhf_tag->epc, uhf_tag->epc_length);
+        length = uhf_tag->epc_length;
+        break;
+    case TID_INFO:
+        temp_str = convertToHexString(uhf_tag->tid, uhf_tag->tid_length);
+        length = uhf_tag->tid_length;
+        break;
+    case USER_INFO:
+        temp_str = convertToHexString(uhf_tag->user, uhf_tag->user_length);
+        length = uhf_tag->user_length;
+        break;
+    default:
+        temp_str = NULL;
+        length = 0;
+        break;
+    }
+
+    furi_string_cat_printf(furi_temp_str, "Length: %d bytes", length);
+
+    widget_add_string_element(
+        uhf_app->widget,
+        3,
+        12,
+        AlignLeft,
+        AlignTop,
+        FontKeyboard,
+        furi_string_get_cstr(furi_temp_str));
+
+    widget_add_string_multiline_element(
+        uhf_app->widget, 3, 24, AlignLeft, AlignTop, FontBatteryPercent, temp_str);
+
+    widget_add_button_element(
+        uhf_app->widget,
+        GuiButtonTypeRight,
+        get_next_bank_info_str(),
+        uhf_scene_device_info_widget_callback,
+        uhf_app);
+
+    widget_add_button_element(
+        uhf_app->widget, GuiButtonTypeLeft, "Back", uhf_scene_device_info_widget_callback, uhf_app);
+
+    furi_string_free(furi_temp_str);
+    free(temp_str);
+}
+
+void uhf_scene_device_info_on_enter(void* context) {
+    UHFApp* uhf_app = context;
+    current_info = EPC_INFO;
+    dolphin_deed(DolphinDeedNfcReadSuccess);
+    change_view_on_event(uhf_app);
+    view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewWidget);
+}
+
+bool uhf_scene_device_info_on_event(void* context, SceneManagerEvent event) {
+    UHFApp* uhf_app = context;
+    bool consumed = false;
+    if(event.type == SceneManagerEventTypeTick) return false;
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == GuiButtonTypeLeft) {
+            consumed = scene_manager_previous_scene(uhf_app->scene_manager);
+        } else if(event.event == GuiButtonTypeRight) {
+            change_view_on_event(uhf_app);
+        } else if(event.event == UHFCustomEventViewExit) {
+            view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewWidget);
+            consumed = true;
+        }
+    } else if(event.type == SceneManagerEventTypeBack) {
+        scene_manager_previous_scene(uhf_app->scene_manager);
+        consumed = true;
+    }
+    return consumed;
+}
+
+void uhf_scene_device_info_on_exit(void* context) {
+    UHFApp* uhf_app = context;
+
+    // Clear views
+    widget_reset(uhf_app->widget);
+}

+ 23 - 0
non_catalog_apps/uhf_rfid/scenes/uhf_scene_file_select.c

@@ -0,0 +1,23 @@
+#include "../uhf_app_i.h"
+
+void uhf_scene_file_select_on_enter(void* context) {
+    UHFApp* uhf_app = context;
+    // Process file_select return
+    uhf_device_set_loading_callback(uhf_app->uhf_device, uhf_show_loading_popup, uhf_app);
+    if(uhf_file_select(uhf_app->uhf_device)) {
+        scene_manager_next_scene(uhf_app->scene_manager, UHFSceneSavedMenu);
+    } else {
+        scene_manager_search_and_switch_to_previous_scene(uhf_app->scene_manager, UHFSceneStart);
+    }
+    uhf_device_set_loading_callback(uhf_app->uhf_device, NULL, uhf_app);
+}
+
+bool uhf_scene_file_select_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+
+void uhf_scene_file_select_on_exit(void* context) {
+    UNUSED(context);
+}

+ 49 - 0
non_catalog_apps/uhf_rfid/scenes/uhf_scene_read_tag.c

@@ -0,0 +1,49 @@
+#include "../uhf_app_i.h"
+#include <dolphin/dolphin.h>
+
+void uhf_read_tag_worker_callback(UHFWorkerEvent event, void* ctx) {
+    UHFApp* uhf_app = ctx;
+    if(event == UHFWorkerEventSuccess) {
+        view_dispatcher_send_custom_event(uhf_app->view_dispatcher, UHFCustomEventWorkerExit);
+    }
+    // } else if(event == UHFWorkerEventAborted) {
+    //     scene_manager_search_and_switch_to_previous_scene(uhf_app->scene_manager, UHFSceneStart);
+    // }
+}
+
+void uhf_scene_read_tag_on_enter(void* ctx) {
+    UHFApp* uhf_app = ctx;
+    dolphin_deed(DolphinDeedNfcRead);
+
+    // Setup view
+    Popup* popup = uhf_app->popup;
+    popup_set_header(popup, "Detecting\n[UHF] RFID\nTag", 68, 30, AlignLeft, AlignTop);
+    popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
+
+    // Start worker
+    view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewPopup);
+    uhf_worker_start(
+        uhf_app->worker, UHFWorkerStateDetectSingle, uhf_read_tag_worker_callback, uhf_app);
+
+    uhf_blink_start(uhf_app);
+}
+
+bool uhf_scene_read_tag_on_event(void* ctx, SceneManagerEvent event) {
+    UHFApp* uhf_app = ctx;
+    bool consumed = false;
+    if(event.event == UHFCustomEventWorkerExit) {
+        scene_manager_next_scene(uhf_app->scene_manager, UHFSceneReadTagSuccess);
+        consumed = true;
+    }
+    return consumed;
+}
+
+void uhf_scene_read_tag_on_exit(void* ctx) {
+    UHFApp* uhf_app = ctx;
+    // Stop worker
+    uhf_worker_stop(uhf_app->worker);
+    // Clear view
+    popup_reset(uhf_app->popup);
+
+    uhf_blink_stop(uhf_app);
+}

+ 94 - 0
non_catalog_apps/uhf_rfid/scenes/uhf_scene_read_tag_success.c

@@ -0,0 +1,94 @@
+#include "../uhf_app_i.h"
+#include <dolphin/dolphin.h>
+
+void uhf_read_tag_success_worker_callback(UHFWorkerEvent event, void* ctx) {
+    UNUSED(event);
+    UNUSED(ctx);
+}
+
+void uhf_scene_read_card_success_widget_callback(GuiButtonType result, InputType type, void* ctx) {
+    furi_assert(ctx);
+    UHFApp* uhf_app = ctx;
+
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(uhf_app->view_dispatcher, result);
+    }
+}
+
+void uhf_scene_read_tag_success_on_enter(void* ctx) {
+    UHFApp* uhf_app = ctx;
+    UHFTag* uhf_tag = uhf_app->worker->uhf_tag;
+
+    dolphin_deed(DolphinDeedNfcReadSuccess);
+
+    // Send notification
+    notification_message(uhf_app->notifications, &sequence_success);
+
+    widget_add_string_element(
+        uhf_app->widget, 32, 5, AlignLeft, AlignCenter, FontPrimary, "Read Success");
+
+    widget_add_string_element(uhf_app->widget, 3, 18, AlignLeft, AlignCenter, FontPrimary, "PC :");
+
+    widget_add_string_element(
+        uhf_app->widget, 66, 18, AlignLeft, AlignCenter, FontPrimary, "CRC :");
+
+    widget_add_string_element(
+        uhf_app->widget, 3, 32, AlignLeft, AlignCenter, FontPrimary, "EPC :");
+
+    char* pc = convertToHexString(uhf_tag->pc, 2);
+    widget_add_string_element(uhf_app->widget, 26, 19, AlignLeft, AlignCenter, FontKeyboard, pc);
+    char* crc = convertToHexString(uhf_tag->crc, 2);
+    widget_add_string_element(uhf_app->widget, 96, 19, AlignLeft, AlignCenter, FontKeyboard, crc);
+    char* epc = convertToHexString(uhf_tag->epc + 2, uhf_tag->epc_length - 2);
+    widget_add_string_multiline_element(
+        uhf_app->widget, 34, 29, AlignLeft, AlignTop, FontKeyboard, epc);
+    widget_add_button_element(
+        uhf_app->widget,
+        GuiButtonTypeRight,
+        "More",
+        uhf_scene_read_card_success_widget_callback,
+        uhf_app);
+    widget_add_button_element(
+        uhf_app->widget,
+        GuiButtonTypeLeft,
+        "Exit",
+        uhf_scene_read_card_success_widget_callback,
+        uhf_app);
+    view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewWidget);
+    free(pc);
+    free(crc);
+    free(epc);
+}
+
+bool uhf_scene_read_tag_success_on_event(void* ctx, SceneManagerEvent event) {
+    UHFApp* uhf_app = ctx;
+    bool consumed = false;
+    if(event.event == SceneManagerEventTypeBack) {
+        uhf_app->worker->state = UHFWorkerStateStop;
+    }
+    if(event.type == SceneManagerEventTypeCustom) {
+        // if 'exit' is pressed go back to home screen
+        if(event.event == GuiButtonTypeLeft) {
+            consumed = scene_manager_search_and_switch_to_previous_scene(
+                uhf_app->scene_manager, UHFSceneStart);
+        } else if(event.event == GuiButtonTypeRight) {
+            scene_manager_next_scene(uhf_app->scene_manager, UHFSceneCardMenu);
+            consumed = true;
+        } else if(event.event == GuiButtonTypeCenter) {
+            // consumed = scene_manager_search_and_switch_to_another_scene(
+            //     picopass->scene_manager, PicopassSceneStart);
+        }
+    }
+    return consumed;
+}
+
+void uhf_scene_read_tag_success_on_exit(void* ctx) {
+    UHFApp* uhf_app = ctx;
+
+    // // Stop worker
+    uhf_worker_stop(uhf_app->worker);
+    // Clear view
+    popup_reset(uhf_app->popup);
+    // clear widget
+    widget_reset(uhf_app->widget);
+}

+ 74 - 0
non_catalog_apps/uhf_rfid/scenes/uhf_scene_save_name.c

@@ -0,0 +1,74 @@
+#include "../uhf_app_i.h"
+#include <lib/toolbox/random_name.h>
+#include <gui/modules/validators.h>
+#include <toolbox/path.h>
+
+void uhf_scene_save_name_text_input_callback(void* context) {
+    UHFApp* uhf_app = context;
+
+    view_dispatcher_send_custom_event(uhf_app->view_dispatcher, UHFCustomEventTextInputDone);
+}
+
+void uhf_scene_save_name_on_enter(void* context) {
+    UHFApp* uhf_app = context;
+
+    // Setup view
+    TextInput* text_input = uhf_app->text_input;
+    set_random_name(uhf_app->text_store, sizeof(uhf_app->text_store));
+    text_input_set_header_text(text_input, "Name the tag");
+    text_input_set_result_callback(
+        text_input,
+        uhf_scene_save_name_text_input_callback,
+        uhf_app,
+        uhf_app->text_store,
+        UHF_DEV_NAME_MAX_LEN,
+        true);
+
+    FuriString* folder_path;
+    folder_path = furi_string_alloc_set(STORAGE_APP_DATA_PATH_PREFIX);
+
+    if(furi_string_end_with(uhf_app->uhf_device->load_path, UHF_APP_EXTENSION)) {
+        path_extract_dirname(furi_string_get_cstr(uhf_app->uhf_device->load_path), folder_path);
+    }
+
+    ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
+        furi_string_get_cstr(folder_path), UHF_APP_EXTENSION, uhf_app->uhf_device->dev_name);
+    text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
+
+    view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewTextInput);
+
+    furi_string_free(folder_path);
+}
+
+bool uhf_scene_save_name_on_event(void* context, SceneManagerEvent event) {
+    UHFApp* uhf_app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == UHFCustomEventTextInputDone) {
+            strlcpy(
+                uhf_app->uhf_device->dev_name,
+                uhf_app->text_store,
+                strlen(uhf_app->text_store) + 1);
+            if(uhf_device_save(uhf_app->uhf_device, uhf_app->text_store)) {
+                scene_manager_next_scene(uhf_app->scene_manager, UHFSceneSaveSuccess);
+                consumed = true;
+            } else {
+                consumed = scene_manager_search_and_switch_to_previous_scene(
+                    uhf_app->scene_manager, UHFSceneStart);
+            }
+        }
+    }
+    return consumed;
+}
+
+void uhf_scene_save_name_on_exit(void* context) {
+    UHFApp* uhf_app = context;
+
+    // Clear view
+    void* validator_context = text_input_get_validator_callback_context(uhf_app->text_input);
+    text_input_set_validator(uhf_app->text_input, NULL, NULL);
+    validator_is_file_free(validator_context);
+
+    text_input_reset(uhf_app->text_input);
+}

+ 47 - 0
non_catalog_apps/uhf_rfid/scenes/uhf_scene_save_success.c

@@ -0,0 +1,47 @@
+#include "../uhf_app_i.h"
+#include <dolphin/dolphin.h>
+
+void uhf_scene_save_success_popup_callback(void* context) {
+    UHFApp* uhf_app = context;
+    view_dispatcher_send_custom_event(uhf_app->view_dispatcher, UHFCustomEventViewExit);
+}
+
+void uhf_scene_save_success_on_enter(void* context) {
+    UHFApp* uhf_app = context;
+    dolphin_deed(DolphinDeedNfcSave);
+
+    // Setup view
+    Popup* popup = uhf_app->popup;
+    popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
+    popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom);
+    popup_set_timeout(popup, 1500);
+    popup_set_context(popup, uhf_app);
+    popup_set_callback(popup, uhf_scene_save_success_popup_callback);
+    popup_enable_timeout(popup);
+    view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewPopup);
+}
+
+bool uhf_scene_save_success_on_event(void* context, SceneManagerEvent event) {
+    UHFApp* uhf_app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == UHFCustomEventViewExit) {
+            if(scene_manager_has_previous_scene(uhf_app->scene_manager, UHFSceneCardMenu)) {
+                consumed = scene_manager_search_and_switch_to_previous_scene(
+                    uhf_app->scene_manager, UHFSceneCardMenu);
+            } else {
+                consumed = scene_manager_search_and_switch_to_previous_scene(
+                    uhf_app->scene_manager, UHFSceneStart);
+            }
+        }
+    }
+    return consumed;
+}
+
+void uhf_scene_save_success_on_exit(void* context) {
+    UHFApp* uhf_app = context;
+
+    // Clear view
+    popup_reset(uhf_app->popup);
+}

+ 60 - 0
non_catalog_apps/uhf_rfid/scenes/uhf_scene_saved_menu.c

@@ -0,0 +1,60 @@
+#include "../uhf_app_i.h"
+
+enum SubmenuIndex {
+    SubmenuIndexDelete,
+    SubmenuIndexInfo,
+    SubmenuIndexWrite,
+};
+
+void uhf_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
+    UHFApp* uhf_app = context;
+
+    view_dispatcher_send_custom_event(uhf_app->view_dispatcher, index);
+}
+
+void uhf_scene_saved_menu_on_enter(void* context) {
+    UHFApp* uhf_app = context;
+    Submenu* submenu = uhf_app->submenu;
+
+    submenu_add_item(
+        submenu, "Delete", SubmenuIndexDelete, uhf_scene_saved_menu_submenu_callback, uhf_app);
+    submenu_add_item(
+        submenu, "Info", SubmenuIndexInfo, uhf_scene_saved_menu_submenu_callback, uhf_app);
+    submenu_add_item(
+        submenu, "Write", SubmenuIndexWrite, uhf_scene_saved_menu_submenu_callback, uhf_app);
+
+    submenu_set_selected_item(
+        uhf_app->submenu,
+        scene_manager_get_scene_state(uhf_app->scene_manager, UHFSceneSavedMenu));
+
+    view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewMenu);
+}
+
+bool uhf_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
+    UHFApp* uhf_app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        scene_manager_set_scene_state(uhf_app->scene_manager, UHFSceneSavedMenu, event.event);
+
+        if(event.event == SubmenuIndexDelete) {
+            scene_manager_next_scene(uhf_app->scene_manager, UHFSceneDelete);
+            consumed = true;
+        } else if(event.event == SubmenuIndexInfo) {
+            scene_manager_next_scene(uhf_app->scene_manager, UHFSceneDeviceInfo);
+            consumed = true;
+        }
+        // } else if(event.event == SubmenuIndexWrite) {
+        //     scene_manager_next_scene(uhf_app->scene_manager, UHFSceneWriteCard);
+        //     consumed = true;
+        // }
+    }
+
+    return consumed;
+}
+
+void uhf_scene_saved_menu_on_exit(void* context) {
+    UHFApp* uhf_app = context;
+
+    submenu_reset(uhf_app->submenu);
+}

+ 56 - 0
non_catalog_apps/uhf_rfid/scenes/uhf_scene_start.c

@@ -0,0 +1,56 @@
+#include "../uhf_app_i.h"
+
+enum SubmenuIndex { SubmenuIndexRead, SubmenuIndexSaved, SubmenuIndexSettings };
+
+void uhf_scene_start_submenu_callback(void* ctx, uint32_t index) {
+    UHFApp* uhf_app = ctx;
+    view_dispatcher_send_custom_event(uhf_app->view_dispatcher, index);
+}
+
+void uhf_scene_start_on_enter(void* ctx) {
+    UHFApp* uhf_app = ctx;
+
+    Submenu* submenu = uhf_app->submenu;
+    submenu_add_item(
+        submenu, "Read Tag", SubmenuIndexRead, uhf_scene_start_submenu_callback, uhf_app);
+    submenu_add_item(
+        submenu, "Saved", SubmenuIndexSaved, uhf_scene_start_submenu_callback, uhf_app);
+    submenu_add_item(
+        submenu, "Settings", SubmenuIndexSettings, uhf_scene_start_submenu_callback, uhf_app);
+
+    submenu_set_selected_item(
+        submenu, scene_manager_get_scene_state(uhf_app->scene_manager, UHFSceneStart));
+    view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewMenu);
+}
+
+bool uhf_scene_start_on_event(void* ctx, SceneManagerEvent event) {
+    UHFApp* uhf_app = ctx;
+    bool consumed = false;
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SubmenuIndexRead) {
+            scene_manager_set_scene_state(uhf_app->scene_manager, UHFSceneStart, SubmenuIndexRead);
+            scene_manager_next_scene(uhf_app->scene_manager, UHFSceneReadTag);
+            consumed = true;
+        } else if(event.event == SubmenuIndexSaved) {
+            // Explicitly save state so that the correct item is
+            // reselected if the user cancels loading a file.
+            scene_manager_set_scene_state(
+                uhf_app->scene_manager, UHFSceneStart, SubmenuIndexSaved);
+            scene_manager_next_scene(uhf_app->scene_manager, UHFSceneFileSelect);
+            consumed = true;
+        }
+        // } else if(event.event == SubmenuIndexEliteDictAttack) {
+        //     scene_manager_set_scene_state(
+        //         uhf_app->scene_manager, UHFSceneStart, SubmenuIndexEliteDictAttack);
+        //     scene_manager_next_scene(uhf_app->scene_manager, UHFSceneEliteDictAttack);
+        //     consumed = true;
+        // }
+        // consumed = true;
+    }
+    return consumed;
+}
+
+void uhf_scene_start_on_exit(void* ctx) {
+    UHFApp* uhf_app = ctx;
+    submenu_reset(uhf_app->submenu);
+}

+ 155 - 0
non_catalog_apps/uhf_rfid/scenes/uhf_scene_verify.c

@@ -0,0 +1,155 @@
+#include "../uhf_app_i.h"
+
+bool verify_success = false;
+FuriString* temp_str;
+
+void uhf_scene_verify_callback_event(UHFWorkerEvent event, void* ctx) {
+    UNUSED(ctx);
+    UHFApp* uhf_app = ctx;
+    if(event == UHFWorkerEventSuccess) verify_success = true;
+
+    view_dispatcher_send_custom_event(uhf_app->view_dispatcher, UHFCustomEventVerifyDone);
+}
+
+void uhf_scene_verify_widget_callback(GuiButtonType result, InputType type, void* ctx) {
+    furi_assert(ctx);
+    UHFApp* uhf_app = ctx;
+
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(uhf_app->view_dispatcher, result);
+    }
+}
+
+void uhf_scene_verify_on_enter(void* ctx) {
+    UHFApp* uhf_app = ctx;
+    uhf_worker_start(
+        uhf_app->worker, UHFWorkerStateVerify, uhf_scene_verify_callback_event, uhf_app);
+    temp_str = furi_string_alloc();
+    view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewWidget);
+}
+
+bool uhf_scene_verify_on_event(void* ctx, SceneManagerEvent event) {
+    UHFApp* uhf_app = ctx;
+    bool consumed = false;
+    if(event.event == SceneManagerEventTypeBack) {
+        uhf_app->worker->state = UHFWorkerStateStop;
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == GuiButtonTypeRight) {
+            scene_manager_next_scene(uhf_app->scene_manager, UHFSceneStart);
+            consumed = true;
+        } else if(event.event == GuiButtonTypeLeft) {
+            if(!verify_success) {
+                widget_reset(uhf_app->widget);
+                furi_string_reset(temp_str);
+                uhf_worker_stop(uhf_app->worker);
+                uhf_worker_start(
+                    uhf_app->worker,
+                    UHFWorkerStateVerify,
+                    uhf_scene_verify_callback_event,
+                    uhf_app);
+            }
+        } else if(event.event == UHFCustomEventVerifyDone) {
+            if(verify_success) {
+                widget_reset(uhf_app->widget);
+                furi_string_reset(temp_str);
+                UHFResponseData* uhf_response_data = uhf_app->worker->response_data;
+                UHFData* hardware_version = uhf_response_data_get_uhf_data(uhf_response_data, 0);
+                UHFData* software_version = uhf_response_data_get_uhf_data(uhf_response_data, 1);
+                UHFData* manufacturer = uhf_response_data_get_uhf_data(uhf_response_data, 2);
+                uint offset = 6;
+                widget_add_string_element(
+                    uhf_app->widget, 64, 5, AlignCenter, AlignCenter, FontPrimary, "Module Info");
+                // hardware info
+                furi_string_cat_str(temp_str, "HW Version: ");
+                for(int i = 0; i < (int)hardware_version->data[4]; i++) {
+                    furi_string_cat_printf(temp_str, "%c", hardware_version->data[offset + i]);
+                }
+                widget_add_string_element(
+                    uhf_app->widget,
+                    1,
+                    15,
+                    AlignLeft,
+                    AlignTop,
+                    FontSecondary,
+                    furi_string_get_cstr(temp_str));
+                furi_string_reset(temp_str);
+                // software info
+                furi_string_cat_str(temp_str, "SW Version: ");
+                for(int i = 0; i < (int)software_version->data[4]; i++) {
+                    furi_string_cat_printf(temp_str, "%c", software_version->data[offset + i]);
+                }
+                widget_add_string_element(
+                    uhf_app->widget,
+                    1,
+                    27,
+                    AlignLeft,
+                    AlignTop,
+                    FontSecondary,
+                    furi_string_get_cstr(temp_str));
+                furi_string_reset(temp_str);
+                // manufacturer info
+                furi_string_cat_str(temp_str, "Manufacturer: ");
+                for(int i = 0; i < (int)manufacturer->data[4]; i++) {
+                    furi_string_cat_printf(temp_str, "%c", manufacturer->data[offset + i]);
+                }
+                widget_add_string_element(
+                    uhf_app->widget,
+                    1,
+                    39,
+                    AlignLeft,
+                    AlignTop,
+                    FontSecondary,
+                    furi_string_get_cstr(temp_str));
+
+                widget_add_button_element(
+                    uhf_app->widget,
+                    GuiButtonTypeRight,
+                    "Continue",
+                    uhf_scene_verify_widget_callback,
+                    uhf_app);
+            } else {
+                widget_add_string_element(
+                    uhf_app->widget,
+                    64,
+                    5,
+                    AlignCenter,
+                    AlignCenter,
+                    FontPrimary,
+                    "No UHF Module found");
+                widget_add_string_multiline_element(
+                    uhf_app->widget,
+                    64,
+                    30,
+                    AlignCenter,
+                    AlignCenter,
+                    FontSecondary,
+                    "Please connect your module.\nPlease refer to the frux-c/uhf_rfid for help.");
+                widget_add_button_element(
+                    uhf_app->widget,
+                    GuiButtonTypeLeft,
+                    "Retry",
+                    uhf_scene_verify_widget_callback,
+                    uhf_app);
+                widget_add_button_element(
+                    uhf_app->widget,
+                    GuiButtonTypeRight,
+                    "Skip",
+                    uhf_scene_verify_widget_callback,
+                    uhf_app);
+            }
+        }
+    }
+    return consumed;
+}
+
+void uhf_scene_verify_on_exit(void* ctx) {
+    UHFApp* uhf_app = ctx;
+    // Clear string
+    furi_string_free(temp_str);
+    // Stop worker
+    uhf_worker_stop(uhf_app->worker);
+    // Clear view
+    // popup_reset(uhf_app->popup);
+    // clear widget
+    widget_reset(uhf_app->widget);
+}

BIN
non_catalog_apps/uhf_rfid/uhf_10px.png


+ 203 - 0
non_catalog_apps/uhf_rfid/uhf_app.c

@@ -0,0 +1,203 @@
+#include "uhf_app_i.h"
+
+char* convertToHexString(const uint8_t* array, size_t length) {
+    if(array == NULL || length == 0) {
+        return " ";
+    }
+    FuriString* temp_str = furi_string_alloc();
+
+    for(size_t i = 0; i < length; i++) {
+        furi_string_cat_printf(temp_str, "%02X ", array[i]);
+    }
+    const char* furi_str = furi_string_get_cstr(temp_str);
+
+    size_t str_len = strlen(furi_str);
+    char* str = (char*)malloc(sizeof(char) * str_len);
+
+    memcpy(str, furi_str, str_len);
+    furi_string_free(temp_str);
+    return str;
+}
+
+bool uhf_custom_event_callback(void* ctx, uint32_t event) {
+    furi_assert(ctx);
+    UHFApp* uhf_app = ctx;
+    return scene_manager_handle_custom_event(uhf_app->scene_manager, event);
+}
+
+bool uhf_back_event_callback(void* ctx) {
+    furi_assert(ctx);
+    UHFApp* uhf_app = ctx;
+    return scene_manager_handle_back_event(uhf_app->scene_manager);
+}
+
+void uhf_tick_event_callback(void* ctx) {
+    furi_assert(ctx);
+    UHFApp* uhf_app = ctx;
+    scene_manager_handle_tick_event(uhf_app->scene_manager);
+}
+
+UHFApp* uhf_alloc() {
+    UHFApp* uhf_app = (UHFApp*)malloc(sizeof(UHFApp));
+    uhf_app->view_dispatcher = view_dispatcher_alloc();
+    uhf_app->scene_manager = scene_manager_alloc(&uhf_scene_handlers, uhf_app);
+    view_dispatcher_enable_queue(uhf_app->view_dispatcher);
+    view_dispatcher_set_event_callback_context(uhf_app->view_dispatcher, uhf_app);
+    view_dispatcher_set_custom_event_callback(uhf_app->view_dispatcher, uhf_custom_event_callback);
+    view_dispatcher_set_navigation_event_callback(
+        uhf_app->view_dispatcher, uhf_back_event_callback);
+    view_dispatcher_set_tick_event_callback(
+        uhf_app->view_dispatcher, uhf_tick_event_callback, 100);
+
+    // Open GUI record
+    uhf_app->gui = furi_record_open(RECORD_GUI);
+    view_dispatcher_attach_to_gui(
+        uhf_app->view_dispatcher, uhf_app->gui, ViewDispatcherTypeFullscreen);
+
+    //worker
+    uhf_app->worker = uhf_worker_alloc();
+
+    // device
+    uhf_app->uhf_device = uhf_device_alloc();
+
+    UHFTag* uhf_tag = uhf_tag_alloc();
+    // point tag object to worker
+    uhf_app->worker->uhf_tag = uhf_tag;
+    uhf_app->uhf_device->uhf_tag = uhf_tag;
+
+    // Open Notification record
+    uhf_app->notifications = furi_record_open(RECORD_NOTIFICATION);
+
+    // Submenu
+    uhf_app->submenu = submenu_alloc();
+    view_dispatcher_add_view(
+        uhf_app->view_dispatcher, UHFViewMenu, submenu_get_view(uhf_app->submenu));
+
+    // Popup
+    uhf_app->popup = popup_alloc();
+    view_dispatcher_add_view(
+        uhf_app->view_dispatcher, UHFViewPopup, popup_get_view(uhf_app->popup));
+
+    // Loading
+    uhf_app->loading = loading_alloc();
+    view_dispatcher_add_view(
+        uhf_app->view_dispatcher, UHFViewLoading, loading_get_view(uhf_app->loading));
+
+    // Text Input
+    uhf_app->text_input = text_input_alloc();
+    view_dispatcher_add_view(
+        uhf_app->view_dispatcher, UHFViewTextInput, text_input_get_view(uhf_app->text_input));
+
+    // Custom Widget
+    uhf_app->widget = widget_alloc();
+    view_dispatcher_add_view(
+        uhf_app->view_dispatcher, UHFViewWidget, widget_get_view(uhf_app->widget));
+
+    return uhf_app;
+}
+
+void uhf_free(UHFApp* uhf_app) {
+    furi_assert(uhf_app);
+
+    // Submenu
+    view_dispatcher_remove_view(uhf_app->view_dispatcher, UHFViewMenu);
+    submenu_free(uhf_app->submenu);
+
+    // Popup
+    view_dispatcher_remove_view(uhf_app->view_dispatcher, UHFViewPopup);
+    popup_free(uhf_app->popup);
+
+    // Loading
+    view_dispatcher_remove_view(uhf_app->view_dispatcher, UHFViewLoading);
+    loading_free(uhf_app->loading);
+
+    // TextInput
+    view_dispatcher_remove_view(uhf_app->view_dispatcher, UHFViewTextInput);
+    text_input_free(uhf_app->text_input);
+
+    // Custom Widget
+    view_dispatcher_remove_view(uhf_app->view_dispatcher, UHFViewWidget);
+    widget_free(uhf_app->widget);
+
+    // Worker
+    uhf_worker_stop(uhf_app->worker);
+    uhf_worker_free(uhf_app->worker);
+
+    // Tag
+    uhf_tag_free(uhf_app->worker->uhf_tag);
+
+    // View Dispatcher
+    view_dispatcher_free(uhf_app->view_dispatcher);
+
+    // Scene Manager
+    scene_manager_free(uhf_app->scene_manager);
+
+    // GUI
+    furi_record_close(RECORD_GUI);
+    uhf_app->gui = NULL;
+
+    // UHFDevice
+    uhf_device_free(uhf_app->uhf_device);
+
+    // Notifications
+    furi_record_close(RECORD_NOTIFICATION);
+    uhf_app->notifications = NULL;
+
+    free(uhf_app);
+}
+
+static const NotificationSequence uhf_sequence_blink_start_cyan = {
+    &message_blink_start_10,
+    &message_blink_set_color_cyan,
+    &message_do_not_reset,
+    NULL,
+};
+
+static const NotificationSequence uhf_sequence_blink_stop = {
+    &message_blink_stop,
+    NULL,
+};
+
+void uhf_blink_start(UHFApp* uhf_app) {
+    notification_message(uhf_app->notifications, &uhf_sequence_blink_start_cyan);
+}
+
+void uhf_blink_stop(UHFApp* uhf_app) {
+    notification_message(uhf_app->notifications, &uhf_sequence_blink_stop);
+}
+
+void uhf_show_loading_popup(void* ctx, bool show) {
+    UHFApp* uhf_app = ctx;
+    TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME);
+
+    if(show) {
+        // Raise timer priority so that animations can play
+        vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1);
+        view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewLoading);
+    } else {
+        // Restore default timer priority
+        vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY);
+    }
+}
+
+int32_t uhf_app_main(void* ctx) {
+    UNUSED(ctx);
+    UHFApp* uhf_app = uhf_alloc();
+
+    furi_hal_uart_resume(FuriHalUartIdUSART1);
+
+    // enable 5v pin
+    furi_hal_power_enable_otg();
+
+    scene_manager_next_scene(uhf_app->scene_manager, UHFSceneVerify);
+    view_dispatcher_run(uhf_app->view_dispatcher);
+
+    // disable 5v pin
+    furi_hal_power_disable_otg();
+
+    furi_hal_uart_suspend(FuriHalUartIdUSART1);
+
+    // exit app
+    uhf_free(uhf_app);
+    return 0;
+}

+ 3 - 0
non_catalog_apps/uhf_rfid/uhf_app.h

@@ -0,0 +1,3 @@
+#pragma once
+
+typedef struct UHFApp UHFApp;

+ 104 - 0
non_catalog_apps/uhf_rfid/uhf_app_i.h

@@ -0,0 +1,104 @@
+#pragma once
+
+#include <furi.h>
+#include <gui/gui.h>
+#include <gui/view_dispatcher.h>
+#include <gui/scene_manager.h>
+#include <notification/notification_messages.h>
+
+#include <gui/modules/submenu.h>
+#include <gui/modules/popup.h>
+#include <gui/modules/loading.h>
+#include <gui/modules/text_input.h>
+#include <gui/modules/widget.h>
+
+#include <input/input.h>
+
+#include "uhf_app.h"
+#include "uhf_worker.h"
+#include "uhf_device.h"
+#include "scenes/uhf_scene.h"
+
+#include <storage/storage.h>
+#include <lib/toolbox/path.h>
+#include <toolbox/path.h>
+#include <flipper_format/flipper_format.h>
+
+#include <uhf_rfid_icons.h>
+
+#define UHF_TEXT_STORE_SIZE 128
+// #define UHF_APPS_DATA_FOLDER EXT_PATH("apps_data")
+// #define UHF_APPS_STORAGE_FOLDER
+//     UHF_APPS_DATA_FOLDER "/"
+//                          "uhf_rfid"
+// #define UHF_FILE_EXTENSION ".uhf"
+
+enum UHFCustomEvent {
+    // Reserve first 100 events for button types and indexes, starting from 0
+    UHFCustomEventReserved = 100,
+
+    UHFCustomEventVerifyDone,
+    UHFCustomEventViewExit,
+    UHFCustomEventWorkerExit,
+    UHFCustomEventByteInputDone,
+    UHFCustomEventTextInputDone,
+};
+
+typedef enum {
+    EventTypeTick,
+    EventTypeKey,
+} EventType;
+
+struct UHFApp {
+    UHFWorker* worker;
+    ViewDispatcher* view_dispatcher;
+    Gui* gui;
+    NotificationApp* notifications;
+    SceneManager* scene_manager;
+    // Storage* storage;
+    UHFDevice* uhf_device;
+    char text_store[UHF_TEXT_STORE_SIZE + 1];
+    FuriString* text_box_store;
+    // Common Views
+    Submenu* submenu;
+    Popup* popup;
+    Loading* loading;
+    TextInput* text_input;
+    Widget* widget;
+};
+
+typedef enum {
+    UHFViewMenu,
+    UHFViewPopup,
+    UHFViewLoading,
+    UHFViewTextInput,
+    UHFViewWidget,
+} UHFView;
+
+UHFApp* uhf_app_alloc();
+
+void uhf_text_store_set(UHFApp* uhf, const char* text, ...);
+
+void uhf_text_store_clear(UHFApp* uhf);
+
+void uhf_blink_start(UHFApp* uhf);
+
+void uhf_blink_stop(UHFApp* uhf);
+
+void uhf_show_loading_popup(void* context, bool show);
+
+/** Check if memory is set to pattern
+ *
+ * @warning    zero size will return false
+ *
+ * @param[in]  data     Pointer to the byte array
+ * @param[in]  pattern  The pattern
+ * @param[in]  size     The byte array size
+ *
+ * @return     True if memory is set to pattern, false otherwise
+ */
+bool uhf_is_memset(const uint8_t* data, const uint8_t pattern, size_t size);
+
+char* convertToHexString(const uint8_t* array, size_t length);
+
+bool uhf_save_read_data(UHFResponseData* uhf_response_data, Storage* storage, const char* filename);

+ 0 - 0
non_catalog_apps/uhf_rfid/uhf_cmd.c


+ 114 - 0
non_catalog_apps/uhf_rfid/uhf_cmd.h

@@ -0,0 +1,114 @@
+#ifndef UHF_CMD_H
+#define UHF_CMD_H
+
+#include <stdint.h>
+#include <stdlib.h>
+
+typedef struct UHF_RFID_CMD {
+    uint8_t* cmd;
+    size_t length;
+}UHF_RFID_CMD;
+
+typedef enum{
+    COMMAND_FRAME = 0x00,
+    RESPONSE_FRAME,
+    NOTIFICATION_FRAME
+}UHFFrameType;
+
+typedef enum{
+    RFU_BANK,
+    EPC_BANK,
+    TID_BANK,
+    USER_BANK
+}UHFBank;
+
+typedef enum{
+    CHINA_900MHZ = 1,
+    CHINA_800MHZ = 4,
+    US = 2,
+    EU = 3,
+    KOREA = 6
+}UHFWorkArea;
+
+#define DEFAULT_BAUD_RATE 115200
+#define FRAME_START 0xBB
+#define FRAME_END 0x7E
+#define ERROR 0x15
+
+UHF_RFID_CMD CMD_HARDWARE_VERSION = {.cmd = (uint8_t[]){0xBB, 0x00, 0x03, 0x00, 0x01, 0x00, 0x04, 0x7E}, .length = 8};
+
+UHF_RFID_CMD CMD_SOFTWARE_VERSION = {.cmd = (uint8_t[]){0xBB, 0x00, 0x03, 0x00, 0x01, 0x01, 0x05, 0x7E}, .length = 8};
+
+UHF_RFID_CMD CMD_MANUFACTURERS = {.cmd = (uint8_t[]){0xBB, 0x00, 0x03, 0x00, 0x01, 0x02, 0x06, 0x7E}, .length = 8};
+
+UHF_RFID_CMD CMD_SINGLE_POLLING = {.cmd = (uint8_t[]){0xBB, 0x00, 0x22, 0x00, 0x00, 0x22, 0x7E}, .length = 7};
+
+UHF_RFID_CMD CMD_MULTIPLE_POLLING = {.cmd = (uint8_t[]){0xBB, 0x00, 0x27, 0x00, 0x03, 0x22, 0x27, 0x10, 0x83, 0x7E}, .length = 10};
+
+UHF_RFID_CMD CMD_STOP_MULTIPLE_POLLING = {.cmd = (uint8_t[]){0xBB, 0x00, 0x28, 0x00, 0x00, 0x28, 0x7E}, .length = 7};
+
+UHF_RFID_CMD CMD_SET_SELECT_PARAMETER = {.cmd = (uint8_t[]){0xBB, 0x00, 0x0C, 0x00, 0x13, 0x01, 0x00, 0x00, 0x00, 0x20, 0x60, 0x00, 0x30, 0x75, 0x1F, 0xEB, 0x70, 0x5C, 0x59, 0x04, 0xE3, 0xD5, 0x0D, 0x70, 0xAD, 0x7E }, .length = 26};
+
+UHF_RFID_CMD CMD_GET_SELECT_PARAMETER = {.cmd = (uint8_t[]){0xBB, 0x00, 0x0B, 0x00, 0x00, 0x0B, 0x7E}, .length = 7};
+
+UHF_RFID_CMD CMD_SET_SELECT_MODE = {.cmd = (uint8_t[]){0xBB, 0x00, 0x12, 0x00, 0x01, 0x01, 0x14, 0x7E}, .length = 8};
+
+UHF_RFID_CMD CMD_READ_LABEL_DATA_STORAGE = {.cmd = (uint8_t[]){0xBB, 0x00, 0x39, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x4D, 0x7E}, .length = 16};
+
+UHF_RFID_CMD CMD_WRITE_LABEL_DATA_STORAGE = {.cmd = (uint8_t[]){0xBB, 0x00, 0x49, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x71, 0x7E}, .length = 24};
+
+UHF_RFID_CMD CMD_LOCK_LABEL_DATA_STORAGE = {.cmd = (uint8_t[]){0xBB, 0x00, 0x82, 0x00, 0x07, 0x00, 0x00, 0xFF, 0xFF, 0x02, 0x00, 0x80, 0x09, 0x7E}, .length = 13};
+
+UHF_RFID_CMD CMD_INACTIVATE_KILL_TAG = {.cmd = (uint8_t[]){0xBB, 0x00, 0x65, 0x00, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x67, 0x7E}, .length = 11};
+
+UHF_RFID_CMD CMD_SET_COMMUNICATION_BAUD_RATE = {.cmd = (uint8_t[]){0xBB, 0x00, 0x11, 0x00, 0x02, 0x00, 0xC0, 0xD3, 0x7E}, .length = 9};
+
+UHF_RFID_CMD CMD_GET_QUERY_PARAMETERS = {.cmd = (uint8_t[]){0xBB, 0x00, 0x0D, 0x00, 0x00, 0x0D, 0x7E}, .length = 7};
+
+UHF_RFID_CMD CMD_SET_QUERY_PARAMETER = {.cmd = (uint8_t[]){0xBB, 0x00, 0x0E, 0x00, 0x02, 0x10, 0x20, 0x40, 0x7E}, .length = 9};
+
+UHF_RFID_CMD CMD_SETUP_WORK_AREA = {.cmd = (uint8_t[]){0xBB, 0x00, 0x07, 0x00, 0x01, 0x01, 0x09, 0x7E}, .length = 8};
+
+UHF_RFID_CMD CMD_ACQUIRE_WORK_LOCATIONS = {.cmd = (uint8_t[]){0xBB, 0x00, 0x08, 0x00, 0x00, 0x08, 0x7E}, .length = 7};
+
+UHF_RFID_CMD CMD_SETUP_WORKING_CHANNEL = {.cmd = (uint8_t[]){0xBB, 0x00, 0xAB, 0x00, 0x01, 0x01, 0xAC, 0x7E}, .length = 8};
+
+UHF_RFID_CMD CMD_GET_WORKING_CHANNEL = {.cmd = (uint8_t[]){0xBB, 0x00, 0xAA, 0x00, 0x00, 0xAA, 0x7E}, .length = 7};
+
+UHF_RFID_CMD CMD_SET_AUTO_FREQUENCY_HOPPING = {.cmd = (uint8_t[]){0xBB, 0x00, 0xAD, 0x00, 0x01, 0xFF, 0xAD, 0x7E}, .length = 8};
+
+UHF_RFID_CMD CMD_INSERT_WORKING_CHANNEL = {.cmd = (uint8_t[]){0xBB, 0x00, 0xA9, 0x00, 0x06, 0x05, 0x01, 0x02,0x03, 0x04, 0x05, 0xC3, 0x7E}, .length = 13};
+
+UHF_RFID_CMD CMD_ACQUIRE_TRANSMITTING_POWER = {.cmd = (uint8_t[]){0xBB, 0x00, 0xB7, 0x00, 0x00, 0xB7, 0x7E}, .length = 7};
+
+UHF_RFID_CMD CMD_SET_TRANSMITTING_POWER = {.cmd = (uint8_t[]){0xBB, 0x00, 0xB6, 0x00, 0x02, 0x07, 0xD0, 0x8F, 0x7E}, .length = 9};
+
+UHF_RFID_CMD CMD_SET_CONTINUOUS_CARRIER = {.cmd = (uint8_t[]){0xBB, 0x00, 0xB0, 0x00, 0x01, 0xFF, 0xB0, 0x7E}, .length = 8};
+
+UHF_RFID_CMD CMD_GET_DEMODULATOR_PARAMETERS = {.cmd = (uint8_t[]){0xBB, 0x00, 0xF1, 0x00, 0x00, 0xF1, 0x7E}, .length = 7};
+
+UHF_RFID_CMD CMD_SET_DEMODULATOR_PARAMETERS = {.cmd = (uint8_t[]){0xBB, 0x00, 0xF0, 0x00, 0x04, 0x03, 0x06, 0x01, 0xB0, 0xAE, 0x7E}, .length = 11};
+
+UHF_RFID_CMD CMD_TEST_RF_INPUT_BLOCK_SIGNAL = {.cmd = (uint8_t[]){0xBB, 0x00, 0xF2, 0x00, 0x00, 0xF2, 0x7E}, .length = 7};
+
+UHF_RFID_CMD CMD_TEST_RSSI_SIGNAL = {.cmd = (uint8_t[]){0xBB, 0x00, 0xF3, 0x00, 0x00, 0xF3, 0x7E}, .length = 7};
+
+UHF_RFID_CMD CMD_MODULE_HIBERNATION = {.cmd = (uint8_t[]){0xBB, 0x00, 0x17, 0x00, 0x00, 0x17, 0x7E}, .length = 7};
+
+UHF_RFID_CMD CMD_IDLE_HIBERNATION_TIME = {.cmd = (uint8_t[]){0xBB, 0x00, 0x1D, 0x00, 0x01, 0x02, 0x20, 0x7E}, .length = 8};
+
+UHF_RFID_CMD CMD_IDLE_MODE = {.cmd = (uint8_t[]){0xBB, 0x00, 0x04, 0x00, 0x03, 0x01, 0x01, 0x03, 0x0C, 0x7E}, .length = 10};
+
+UHF_RFID_CMD CMD_NXP_READPROTECT = {.cmd = (uint8_t[]){0xBB, 0x00, 0xE1, 0x00, 0x05, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xE4, 0x7E}, .length = 12};
+
+UHF_RFID_CMD CMD_NXP_CHANGE_EAS = {.cmd = (uint8_t[]){0xBB, 0x00, 0xE3, 0x00, 0x05, 0x00, 0x00, 0xFF, 0xFF, 0x01, 0xE7, 0x7E}, .length = 12};
+
+UHF_RFID_CMD CMD_NXP_EAS_ALARM = {.cmd = (uint8_t[]){0xBB, 0x00, 0xE4, 0x00, 0x00, 0xE4, 0x7E}, .length = 7};
+
+UHF_RFID_CMD CMD_NXP_CONFIG_WORD = {.cmd = (uint8_t[]){0xBB, 0x00, 0xE0, 0x00, 0x06, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xE4, 0x7E}, .length = 13};
+
+UHF_RFID_CMD CMD_IMPINJ_MONZA4_QT = {.cmd = (uint8_t[]){0xBB, 0x00, 0xE5, 0x00, 0x08, 0x00, 0x00, 0xFF, 0xFF, 0x01, 0x01, 0x40, 0x00, 0x2D, 0x7E}, .length = 15};
+
+UHF_RFID_CMD CMD_BLOCK_PERMALOCK = {.cmd = (uint8_t[]){0xBB, 0x00, 0xD3, 0x00, 0x0B, 0x00, 0x00, 0xFF,0xFF, 0x01, 0x03, 0x00, 0x00, 0x01, 0x07, 0x00, 0xE8, 0x7E}, .length = 18};
+
+#endif /* UHF_CMD_H */

+ 148 - 0
non_catalog_apps/uhf_rfid/uhf_data.c

@@ -0,0 +1,148 @@
+#include "uhf_data.h"
+
+UHFData* uhf_data_alloc() {
+    UHFData* uhf_data = (UHFData*)malloc(sizeof(UHFData));
+    uhf_data->word_length = 0;
+    uhf_data->length = 0;
+    uhf_data->start = false;
+    uhf_data->end = false;
+    uhf_data->next = NULL;
+    return uhf_data;
+}
+
+int uhf_data_append(UHFData* uhf_data, uint8_t data) {
+    if(data == 0xBB) {
+        uhf_data->start = true;
+    }
+    if(!uhf_data->start) return 0;
+    if(uhf_data->end) return 0;
+    if(uhf_data->length >= MAX_DATA_SIZE) return 0;
+    if(data == 0x7E) {
+        uhf_data->end = true;
+    }
+    uhf_data->data[uhf_data->length++] = data;
+    return 1;
+}
+
+void uhf_data_reset(UHFData* uhf_data) {
+    for(size_t i = 0; i < uhf_data->length; i++) {
+        uhf_data->data[i] = 0x00;
+    }
+    uhf_data->start = false;
+    uhf_data->end = false;
+    uhf_data->length = 0;
+    uhf_data->next = NULL;
+}
+
+uint8_t uhf_data_calculate_checksum(UHFData* uhf_data) {
+    // CheckSum8 Modulo 256
+    // Sum of Bytes % 256
+    uint8_t sum_val = 0x00;
+    size_t length = uhf_data->length - 2;
+    for(size_t i = 1; i < length; i++) {
+        sum_val += uhf_data->data[i];
+    }
+    return sum_val % 256;
+}
+
+bool uhf_data_verfiy_checksum(UHFData* uhf_data) {
+    uint8_t data_checksum = uhf_data->data[uhf_data->length - 2];
+    uint8_t actual_checksum = uhf_data_calculate_checksum(uhf_data);
+    return data_checksum == actual_checksum;
+}
+
+void uhf_data_free(UHFData* uhf_data) {
+    if(uhf_data == NULL) return;
+    while(uhf_data != NULL) {
+        UHFData* next = uhf_data->next;
+        free(uhf_data);
+        uhf_data = next;
+    }
+}
+
+UHFResponseData* uhf_response_data_alloc() {
+    UHFResponseData* uhf_response_data = (UHFResponseData*)malloc(sizeof(UHFResponseData));
+    uhf_response_data->head = uhf_data_alloc();
+    uhf_response_data->tail = uhf_response_data->head;
+    uhf_response_data->size = 1;
+    return uhf_response_data;
+}
+
+UHFData* uhf_response_data_add_new_uhf_data(UHFResponseData* uhf_response_data) {
+    UHFData* temp = uhf_response_data->head;
+    while(temp->next != NULL) {
+        temp = temp->next;
+    }
+    temp->next = uhf_data_alloc();
+    uhf_response_data->size++;
+    uhf_response_data->tail = temp->next;
+    return temp->next;
+}
+
+UHFData* uhf_response_data_get_uhf_data(UHFResponseData* uhf_response_data, uint index) {
+    if(uhf_response_data == NULL || uhf_response_data->size <= index) return NULL;
+    UHFData* uhf_data = uhf_response_data->head;
+    if(index == 0) return uhf_data;
+    while(uhf_data != NULL && index >= 1) {
+        uhf_data = uhf_data->next;
+        index--;
+    }
+    return uhf_data;
+}
+
+void uhf_response_data_reset(UHFResponseData* uhf_response_data) {
+    uhf_data_reset(uhf_response_data->head);
+    if(uhf_response_data->size == 1) {
+        return;
+    }
+    uhf_data_free(uhf_response_data->head->next);
+    uhf_response_data->size = 1;
+}
+
+void uhf_response_data_free(UHFResponseData* uhf_response_data) {
+    uhf_data_free(uhf_response_data->head);
+    free(uhf_response_data);
+}
+
+UHFTag* uhf_tag_alloc() {
+    UHFTag* uhf_tag = (UHFTag*)malloc(sizeof(UHFTag));
+    return uhf_tag;
+}
+
+void uhf_tag_set_epc(UHFTag* uhf_tag, uint8_t* data, size_t length) {
+    memcpy(uhf_tag->crc, data, 2);
+    data += 2;
+    memcpy(uhf_tag->pc, data, 2);
+    memcpy(uhf_tag->epc, data, length);
+    uhf_tag->epc_length = length;
+}
+
+void uhf_tag_reset(UHFTag* uhf_tag) {
+    for(int i = 0; i < 2; i++) {
+        uhf_tag->crc[i] = 0;
+        uhf_tag->pc[i] = 0;
+        uhf_tag->xpc[i] = 0;
+    }
+    for(int i = 0; i < MAX_BANK_SIZE; i++) {
+        uhf_tag->epc[i] = 0;
+        uhf_tag->tid[i] = 0;
+        uhf_tag->user[i] = 0;
+    }
+    uhf_tag->epc_length = 0;
+    uhf_tag->tid_length = 0;
+    uhf_tag->user_length = 0;
+}
+
+void uhf_tag_set_tid(UHFTag* uhf_tag, uint8_t* data, size_t length) {
+    memcpy(uhf_tag->tid, data, length);
+    uhf_tag->tid_length = length;
+}
+
+void uhf_tag_set_user(UHFTag* uhf_tag, uint8_t* data, size_t length) {
+    memcpy(uhf_tag->user, data, length);
+    uhf_tag->user_length = length;
+}
+
+void uhf_tag_free(UHFTag* uhf_tag) {
+    free(uhf_tag);
+}

+ 64 - 0
non_catalog_apps/uhf_rfid/uhf_data.h

@@ -0,0 +1,64 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+
+#define MAX_DATA_SIZE 128
+#define MAX_BANK_SIZE 64
+typedef unsigned int uint;
+
+typedef struct UHFData {
+    uint8_t data[MAX_DATA_SIZE];
+    size_t word_length;
+    size_t length;
+    bool start;
+    bool end;
+    struct UHFData* next;
+} UHFData;
+
+typedef struct UHFResponseData {
+    UHFData* head;
+    UHFData* tail;
+    size_t size;
+} UHFResponseData;
+
+typedef struct UHFTag {
+    // RESERVED BANK (RFU) (00)
+    uint8_t kill_pwd[2]; // 0x00-0x10
+    uint8_t access_pwd[2]; // 0x10-0x20
+    // EPC Bank
+    uint8_t crc[2]; // 0x00-0x10
+    uint8_t pc[2]; // 0x10-0x20
+    uint8_t epc[MAX_BANK_SIZE]; // 0x20-0x210
+    size_t epc_length;
+    uint8_t xpc[2]; // 0x210-0x21F
+    size_t xpc_length;
+    // TID Bank
+    uint8_t tid[MAX_BANK_SIZE]; // 0x00-END
+    size_t tid_length;
+    // USER Bank
+    uint8_t user[MAX_BANK_SIZE]; // 0x00-END
+    size_t user_length;
+} UHFTag;
+
+UHFData* uhf_data_alloc();
+int uhf_data_append(UHFData* uhf_data, uint8_t data);
+void uhf_data_reset(UHFData* uhf_data);
+uint8_t uhf_data_calculate_checksum(UHFData* uhf_data);
+bool uhf_data_verfiy_checksum(UHFData* uhf_data);
+void uhf_data_free(UHFData* uhf_data);
+
+UHFResponseData* uhf_response_data_alloc();
+UHFData* uhf_response_data_add_new_uhf_data(UHFResponseData* uhf_response_data);
+UHFData* uhf_response_data_get_uhf_data(UHFResponseData* uhf_response_data, uint index);
+void uhf_response_data_reset(UHFResponseData* uhf_response_data);
+void uhf_response_data_free(UHFResponseData* uhf_response_data);
+
+UHFTag* uhf_tag_alloc();
+void uhf_tag_reset(UHFTag* uhf_tag);
+void uhf_tag_set_epc(UHFTag* uhf_tag, uint8_t* data, size_t length);
+void uhf_tag_set_tid(UHFTag* uhf_tag, uint8_t* data, size_t length);
+void uhf_tag_set_user(UHFTag* uhf_tag, uint8_t* data, size_t length);
+void uhf_tag_free(UHFTag* uhf_tag);

+ 2 - 0
non_catalog_apps/uhf_rfid/uhf_data_i.h

@@ -0,0 +1,2 @@
+// todo : probably will move some of the uhf_data functions to internal only
+// once i figure out how to structure the method calls

+ 325 - 0
non_catalog_apps/uhf_rfid/uhf_device.c

@@ -0,0 +1,325 @@
+#include "uhf_device.h"
+
+#include <toolbox/path.h>
+#include <flipper_format/flipper_format.h>
+#include <uhf_rfid_icons.h>
+
+#define TAG "UHFDevice"
+
+static const char* uhf_file_header = "Flipper UHF RFID device";
+static const uint32_t uhf_file_version = 1;
+// static const uint8_t bank_data_start = 20;
+// static const uint8_t bank_data_length = 16;
+
+UHFDevice* uhf_device_alloc() {
+    UHFDevice* uhf_device = malloc(sizeof(UHFDevice));
+    uhf_device->storage = furi_record_open(RECORD_STORAGE);
+    uhf_device->dialogs = furi_record_open(RECORD_DIALOGS);
+    uhf_device->load_path = furi_string_alloc();
+    return uhf_device;
+}
+
+void uhf_device_set_name(UHFDevice* dev, const char* name) {
+    furi_assert(dev);
+
+    strlcpy(dev->dev_name, name, UHF_DEV_NAME_MAX_LEN);
+}
+
+static bool uhf_device_save_file(
+    UHFDevice* dev,
+    const char* dev_name,
+    const char* folder,
+    const char* extension,
+    bool use_load_path) {
+    furi_assert(dev);
+
+    UHFTag* uhf_tag = dev->uhf_tag;
+    bool saved = false;
+    FlipperFormat* file = flipper_format_file_alloc(dev->storage);
+    FuriString* temp_str;
+    temp_str = furi_string_alloc();
+    do {
+        if(use_load_path && !furi_string_empty(dev->load_path)) {
+            // Get directory name
+            path_extract_dirname(furi_string_get_cstr(dev->load_path), temp_str);
+            // Make path to file to save
+            furi_string_cat_printf(temp_str, "/%s%s", dev_name, extension);
+        } else {
+            // First remove uhf device file if it was saved
+            furi_string_printf(temp_str, "%s/%s%s", folder, dev_name, extension);
+        }
+        // Open file
+        if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break;
+
+        // Write header
+        if(!flipper_format_write_header_cstr(file, uhf_file_header, uhf_file_version)) break;
+
+        // Reserved bank might be added
+        // todo : maybe
+        uint32_t temp_arr[1];
+        // write epc
+        temp_arr[0] = uhf_tag->epc_length;
+        if(!flipper_format_write_uint32(file, UHF_EPC_BANK_LENGTH_LABEL, temp_arr, 1)) break;
+        if(!flipper_format_write_hex(file, UHF_EPC_BANK_LABEL, uhf_tag->epc, uhf_tag->epc_length))
+            break;
+        // write tid
+        temp_arr[0] = uhf_tag->tid_length;
+        if(!flipper_format_write_uint32(file, UHF_TID_BANK_LENGTH_LABEL, temp_arr, 1)) break;
+        if(!flipper_format_write_hex(file, UHF_TID_BANK_LABEL, uhf_tag->tid, uhf_tag->tid_length))
+            break;
+        // write user
+        temp_arr[0] = uhf_tag->user_length;
+        if(!flipper_format_write_uint32(file, UHF_USER_BANK_LENGTH_LABEL, temp_arr, 1)) break;
+        if(!flipper_format_write_hex(
+               file, UHF_USER_BANK_LABEL, uhf_tag->user, uhf_tag->user_length))
+            break;
+        saved = true;
+    } while(0);
+
+    if(!saved) {
+        dialog_message_show_storage_error(dev->dialogs, "Can not save\nfile");
+    }
+    furi_string_free(temp_str);
+    flipper_format_free(file);
+    return saved;
+}
+
+bool uhf_device_save(UHFDevice* dev, const char* dev_name) {
+    return uhf_device_save_file(
+        dev, dev_name, STORAGE_APP_DATA_PATH_PREFIX, UHF_APP_EXTENSION, true);
+
+    return false;
+}
+// uncomment
+
+static bool uhf_device_load_data(UHFDevice* dev, FuriString* path, bool show_dialog) {
+    bool parsed = false;
+    FlipperFormat* file = flipper_format_file_alloc(dev->storage);
+    // UHFResponseData* uhf_response_data = dev->dev_data;
+    FuriString* temp_str;
+    temp_str = furi_string_alloc();
+    bool deprecated_version = false;
+    UHFTag* uhf_tag = dev->uhf_tag;
+    uhf_tag_reset(uhf_tag);
+    uint32_t temp_arr[1];
+    if(dev->loading_cb) {
+        dev->loading_cb(dev->loading_cb_ctx, true);
+    }
+
+    do {
+        if(!flipper_format_file_open_existing(file, furi_string_get_cstr(path))) break;
+
+        // Read and verify file header
+        uint32_t version = 0;
+        if(!flipper_format_read_header(file, temp_str, &version)) break;
+        if(furi_string_cmp_str(temp_str, uhf_file_header) || (version != uhf_file_version)) {
+            deprecated_version = true;
+            break;
+        }
+        // read epc
+        if(!flipper_format_read_uint32(file, UHF_EPC_BANK_LENGTH_LABEL, temp_arr, 1)) break;
+        uhf_tag->epc_length = temp_arr[0];
+        if(!flipper_format_read_hex(file, UHF_EPC_BANK_LABEL, uhf_tag->epc, uhf_tag->epc_length))
+            break;
+
+        // read tid
+        if(!flipper_format_read_uint32(file, UHF_TID_BANK_LENGTH_LABEL, temp_arr, 1)) break;
+        uhf_tag->tid_length = temp_arr[0];
+        if(!flipper_format_read_hex(file, UHF_TID_BANK_LABEL, uhf_tag->tid, uhf_tag->tid_length))
+            break;
+
+        // read user
+        if(!flipper_format_read_uint32(file, UHF_USER_BANK_LENGTH_LABEL, temp_arr, 1)) break;
+        uhf_tag->user_length = temp_arr[0];
+        if(!flipper_format_read_hex(file, UHF_USER_BANK_LABEL, uhf_tag->user, uhf_tag->user_length))
+            break;
+
+        parsed = true;
+    } while(false);
+
+    if(dev->loading_cb) {
+        dev->loading_cb(dev->loading_cb_ctx, false);
+    }
+
+    if((!parsed) && (show_dialog)) {
+        if(deprecated_version) {
+            dialog_message_show_storage_error(dev->dialogs, "File format deprecated");
+        } else {
+            dialog_message_show_storage_error(dev->dialogs, "Can not parse\nfile");
+        }
+    }
+
+    furi_string_free(temp_str);
+    flipper_format_free(file);
+
+    return parsed;
+}
+
+// void picopass_device_clear(UHFDevice* dev) {
+//     furi_assert(dev);
+
+//     picopass_device_data_clear(&dev->dev_data);
+//     memset(&dev->dev_data, 0, sizeof(dev->dev_data));
+//     dev->format = PicopassDeviceSaveFormatHF;
+//     furi_string_reset(dev->load_path);
+// }
+
+void uhf_device_free(UHFDevice* uhf_dev) {
+    furi_assert(uhf_dev);
+    furi_record_close(RECORD_STORAGE);
+    furi_record_close(RECORD_DIALOGS);
+    furi_string_free(uhf_dev->load_path);
+    free(uhf_dev);
+}
+
+bool uhf_file_select(UHFDevice* dev) {
+    furi_assert(dev);
+
+    FuriString* uhf_app_folder;
+    uhf_app_folder = furi_string_alloc_set(STORAGE_APP_DATA_PATH_PREFIX);
+
+    DialogsFileBrowserOptions browser_options;
+    dialog_file_browser_set_basic_options(&browser_options, UHF_APP_EXTENSION, &I_Nfc_10px);
+    browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX;
+
+    bool res =
+        dialog_file_browser_show(dev->dialogs, dev->load_path, uhf_app_folder, &browser_options);
+
+    furi_string_free(uhf_app_folder);
+    if(res) {
+        FuriString* filename;
+        filename = furi_string_alloc();
+        path_extract_filename(dev->load_path, filename, true);
+        strncpy(dev->dev_name, furi_string_get_cstr(filename), UHF_DEV_NAME_MAX_LEN);
+        res = uhf_device_load_data(dev, dev->load_path, true);
+        if(res) {
+            uhf_device_set_name(dev, dev->dev_name);
+        }
+        furi_string_free(filename);
+    }
+
+    return res;
+}
+
+// void uhf_device_data_clear(UHFDevice* dev_data) {
+//     for(size_t i = 0; i < PICOPASS_MAX_APP_LIMIT; i++) {
+//         memset(dev_data->AA1[i].data, 0, sizeof(dev_data->AA1[i].data));
+//     }
+//     dev_data->pacs.legacy = false;
+//     dev_data->pacs.se_enabled = false;
+//     dev_data->pacs.elite_kdf = false;
+//     dev_data->pacs.pin_length = 0;
+// }
+
+bool uhf_device_delete(UHFDevice* dev, bool use_load_path) {
+    furi_assert(dev);
+
+    bool deleted = false;
+    FuriString* file_path;
+    file_path = furi_string_alloc();
+
+    do {
+        // Delete original file
+        if(use_load_path && !furi_string_empty(dev->load_path)) {
+            furi_string_set(file_path, dev->load_path);
+        } else {
+            furi_string_printf(file_path, APP_DATA_PATH("%s%s"), dev->dev_name, UHF_APP_EXTENSION);
+        }
+        if(!storage_simply_remove(dev->storage, furi_string_get_cstr(file_path))) break;
+        deleted = true;
+    } while(0);
+
+    if(!deleted) {
+        dialog_message_show_storage_error(dev->dialogs, "Can not remove file");
+    }
+
+    furi_string_free(file_path);
+    return deleted;
+}
+
+void uhf_device_set_loading_callback(UHFDevice* dev, UHFLoadingCallback callback, void* context) {
+    furi_assert(dev);
+
+    dev->loading_cb = callback;
+    dev->loading_cb_ctx = context;
+}
+
+// ReturnCode picopass_device_decrypt(uint8_t* enc_data, uint8_t* dec_data) {
+//     uint8_t key[32] = {0};
+//     memcpy(key, picopass_iclass_decryptionkey, sizeof(picopass_iclass_decryptionkey));
+//     mbedtls_des3_context ctx;
+//     mbedtls_des3_init(&ctx);
+//     mbedtls_des3_set2key_dec(&ctx, key);
+//     mbedtls_des3_crypt_ecb(&ctx, enc_data, dec_data);
+//     mbedtls_des3_free(&ctx);
+//     return ERR_NONE;
+// }
+
+// ReturnCode picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs) {
+//     ReturnCode err;
+
+//     pacs->biometrics = AA1[6].data[4];
+//     pacs->pin_length = AA1[6].data[6] & 0x0F;
+//     pacs->encryption = AA1[6].data[7];
+
+//     if(pacs->encryption == PicopassDeviceEncryption3DES) {
+//         FURI_LOG_D(TAG, "3DES Encrypted");
+//         err = picopass_device_decrypt(AA1[7].data, pacs->credential);
+//         if(err != ERR_NONE) {
+//             FURI_LOG_E(TAG, "decrypt error %d", err);
+//             return err;
+//         }
+
+//         err = picopass_device_decrypt(AA1[8].data, pacs->pin0);
+//         if(err != ERR_NONE) {
+//             FURI_LOG_E(TAG, "decrypt error %d", err);
+//             return err;
+//         }
+
+//         err = picopass_device_decrypt(AA1[9].data, pacs->pin1);
+//         if(err != ERR_NONE) {
+//             FURI_LOG_E(TAG, "decrypt error %d", err);
+//             return err;
+//         }
+//     } else if(pacs->encryption == PicopassDeviceEncryptionNone) {
+//         FURI_LOG_D(TAG, "No Encryption");
+//         memcpy(pacs->credential, AA1[7].data, PICOPASS_BLOCK_LEN);
+//         memcpy(pacs->pin0, AA1[8].data, PICOPASS_BLOCK_LEN);
+//         memcpy(pacs->pin1, AA1[9].data, PICOPASS_BLOCK_LEN);
+//     } else if(pacs->encryption == PicopassDeviceEncryptionDES) {
+//         FURI_LOG_D(TAG, "DES Encrypted");
+//     } else {
+//         FURI_LOG_D(TAG, "Unknown encryption");
+//     }
+
+//     pacs->sio = (AA1[10].data[0] == 0x30); // rough check
+
+//     return ERR_NONE;
+// }
+
+// ReturnCode picopass_device_parse_wiegand(uint8_t* data, PicopassWiegandRecord* record) {
+//     uint32_t* halves = (uint32_t*)data;
+//     if(halves[0] == 0) {
+//         uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[1]));
+//         record->bitLength = 31 - leading0s;
+//     } else {
+//         uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[0]));
+//         record->bitLength = 63 - leading0s;
+//     }
+//     FURI_LOG_D(TAG, "bitLength: %d", record->bitLength);
+
+//     if(record->bitLength == 26) {
+//         uint8_t* v4 = data + 4;
+//         uint32_t bot = v4[3] | (v4[2] << 8) | (v4[1] << 16) | (v4[0] << 24);
+
+//         record->CardNumber = (bot >> 1) & 0xFFFF;
+//         record->FacilityCode = (bot >> 17) & 0xFF;
+//         FURI_LOG_D(TAG, "FC: %u CN: %u", record->FacilityCode, record->CardNumber);
+//         record->valid = true;
+//     } else {
+//         record->CardNumber = 0;
+//         record->FacilityCode = 0;
+//         record->valid = false;
+//     }
+//     return ERR_NONE;
+// }

+ 65 - 0
non_catalog_apps/uhf_rfid/uhf_device.h

@@ -0,0 +1,65 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <storage/storage.h>
+#include <dialogs/dialogs.h>
+#include <mbedtls/des.h>
+#include "uhf_data.h"
+
+// #include "rfal_picopass.h"
+
+#define UHF_DEV_NAME_MAX_LEN 22
+// #define PICOPASS_READER_DATA_MAX_SIZE 64
+// #define PICOPASS_BLOCK_LEN 8
+// #define PICOPASS_MAX_APP_LIMIT 32
+#define UHF_BANK_DOES_NOT_EXIST                                                                   \
+    (uint8_t[]) {                                                                                 \
+        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, \
+            0xFF                                                                                  \
+    }
+
+#define UHF_EPC_BANK_LENGTH_LABEL "EPC_LENGTH"
+#define UHF_TID_BANK_LENGTH_LABEL "TID_LENGTH"
+#define UHF_USER_BANK_LENGTH_LABEL "USER_LENGTH"
+#define UHF_RFU_BANK_LABEL "RFU"
+#define UHF_EPC_BANK_LABEL "EPC"
+#define UHF_TID_BANK_LABEL "TID"
+#define UHF_USER_BANK_LABEL "USER"
+
+#define UHF_APP_EXTENSION ".uhf"
+// #define PICOPASS_APP_SHADOW_EXTENSION ".pas"
+
+typedef void (*UHFLoadingCallback)(void* context, bool state);
+
+typedef struct {
+    Storage* storage;
+    DialogsApp* dialogs;
+    // UHFResponseData* dev_data;
+    UHFTag* uhf_tag;
+    char dev_name[UHF_DEV_NAME_MAX_LEN + 1];
+    FuriString* load_path;
+    UHFLoadingCallback loading_cb;
+    void* loading_cb_ctx;
+} UHFDevice;
+
+UHFDevice* uhf_device_alloc();
+
+void uhf_device_free(UHFDevice* uhf_dev);
+
+void uhf_device_set_name(UHFDevice* dev, const char* name);
+
+bool uhf_device_save(UHFDevice* dev, const char* dev_name);
+
+bool uhf_file_select(UHFDevice* dev);
+
+// void uhf_device_data_clear(PicopassDeviceData* dev_data);
+
+void uhf_device_clear(UHFDevice* dev);
+
+bool uhf_device_delete(UHFDevice* dev, bool use_load_path);
+
+void uhf_device_set_loading_callback(UHFDevice* dev, UHFLoadingCallback callback, void* context);
+
+// ReturnCode uhf_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs);
+// ReturnCode uhf_device_parse_wiegand(uint8_t* data, PicopassWiegandRecord* record);

+ 256 - 0
non_catalog_apps/uhf_rfid/uhf_worker.c

@@ -0,0 +1,256 @@
+#include "uhf_worker.h"
+#include "uhf_cmd.h"
+
+#define CB_DELAY 50
+
+// uart callback functions
+void module_rx_callback(UartIrqEvent event, uint8_t data, void* ctx) {
+    UNUSED(event);
+    UHFData* uhf_data = ctx;
+    uhf_data_append(uhf_data, data);
+}
+
+// yrm100 module commands
+UHFWorkerEvent verify_module_connected(UHFWorker* uhf_worker) {
+    UHFResponseData* uhf_response_data = uhf_worker->response_data;
+    uhf_response_data_reset(uhf_response_data);
+    // FURI_LOG_E("log", "freeing done");
+    UHFData* hardware_version = uhf_response_data->head;
+    UHFData* software_version = uhf_response_data_add_new_uhf_data(uhf_response_data);
+    UHFData* manufacturer = uhf_response_data_add_new_uhf_data(uhf_response_data);
+    // FURI_LOG_E("log", "alloc done");
+    furi_hal_uart_set_br(FuriHalUartIdUSART1, DEFAULT_BAUD_RATE);
+    // read hardware version
+    furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, module_rx_callback, hardware_version);
+    furi_hal_uart_tx(FuriHalUartIdUSART1, CMD_HARDWARE_VERSION.cmd, CMD_HARDWARE_VERSION.length);
+    furi_delay_ms(CB_DELAY);
+    // read software version
+    furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, module_rx_callback, software_version);
+    furi_hal_uart_tx(FuriHalUartIdUSART1, CMD_SOFTWARE_VERSION.cmd, CMD_SOFTWARE_VERSION.length);
+    furi_delay_ms(CB_DELAY);
+    // read manufacturer
+    furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, module_rx_callback, manufacturer);
+    furi_hal_uart_tx(FuriHalUartIdUSART1, CMD_MANUFACTURERS.cmd, CMD_MANUFACTURERS.length);
+    furi_delay_ms(CB_DELAY);
+    // verify that we received all data
+    if(!hardware_version->end || !software_version->end || !manufacturer->end) {
+        return UHFWorkerEventFail;
+    }
+    // verify all data was received correctly
+    if(!uhf_data_verfiy_checksum(hardware_version) ||
+       !uhf_data_verfiy_checksum(software_version) || !uhf_data_verfiy_checksum(manufacturer))
+        return UHFWorkerEventFail;
+
+    return UHFWorkerEventSuccess;
+}
+
+static uint8_t get_epc_length_in_bits(uint8_t pc) {
+    uint8_t epc_length = pc;
+    epc_length >>= 3;
+    return (uint8_t)epc_length * 16; // x-words * 16 bits
+}
+
+static bool send_set_select_command(UHFData* selected_tag, UHFBank bank) {
+    bool success = false;
+    // Set select
+    UHFData* select_cmd = uhf_data_alloc();
+    select_cmd->start = true;
+    select_cmd->length = CMD_SET_SELECT_PARAMETER.length;
+    memcpy((void*)&select_cmd->data, (void*)&CMD_SET_SELECT_PARAMETER.cmd[0], select_cmd->length);
+    // set select param
+    size_t mask_length_bits = (size_t)get_epc_length_in_bits(selected_tag->data[6]);
+    size_t mask_length_bytes = (size_t)mask_length_bits / 8;
+    select_cmd->data[5] = bank; // 0x00=rfu, 0x01=epc, 0x10=tid, 0x11=user
+    // set ptr
+    select_cmd->data[9] = 0x20; // epc data begins after 0x20
+    // set mask length
+    select_cmd->data[10] = mask_length_bits;
+    // set mask starting position
+    select_cmd->length = 12;
+    // set mask
+    for(size_t i = 0; i < mask_length_bytes; i++) {
+        uhf_data_append(select_cmd, selected_tag->data[8 + i]);
+    }
+    uhf_data_append(select_cmd, 0x00); // add checksum section
+    uhf_data_append(select_cmd, FRAME_END); // command end
+    // add checksum
+    select_cmd->data[select_cmd->length - 2] = uhf_data_calculate_checksum(select_cmd);
+    UHFData* select_response = uhf_data_alloc();
+    furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, module_rx_callback, select_response);
+    furi_hal_uart_tx(FuriHalUartIdUSART1, select_cmd->data, select_cmd->length);
+    furi_delay_ms(CB_DELAY);
+
+    success = select_response->data[5] == 0x00;
+
+    uhf_data_free(select_cmd);
+    uhf_data_free(select_response);
+
+    return success;
+}
+
+static bool read_bank(UHFData* read_bank_cmd, UHFData* response_bank, UHFBank bank) {
+    furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, module_rx_callback, response_bank);
+    read_bank_cmd->data[9] = bank;
+    read_bank_cmd->data[read_bank_cmd->length - 2] = uhf_data_calculate_checksum(read_bank_cmd);
+    uhf_data_reset(response_bank);
+    furi_hal_uart_tx(FuriHalUartIdUSART1, read_bank_cmd->data, read_bank_cmd->length);
+    furi_delay_ms(CB_DELAY);
+    return response_bank->data[2] == read_bank_cmd->data[2];
+}
+
+UHFWorkerEvent read_single_card(UHFWorker* uhf_worker) {
+    // debug
+    // FuriString* temp_str;
+    // temp_str = furi_string_alloc();
+    // e-debug
+    UHFResponseData* uhf_response_data = uhf_worker->response_data;
+    uhf_response_data_reset(uhf_response_data);
+    UHFData* raw_read_data = uhf_response_data_get_uhf_data(uhf_response_data, 0);
+    furi_hal_uart_set_br(FuriHalUartIdUSART1, DEFAULT_BAUD_RATE);
+    furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, module_rx_callback, raw_read_data);
+    uhf_data_reset(raw_read_data);
+    // read epc bank
+    while(true) {
+        furi_hal_uart_tx(FuriHalUartIdUSART1, CMD_SINGLE_POLLING.cmd, CMD_SINGLE_POLLING.length);
+        furi_delay_ms(100);
+        if(uhf_worker->state == UHFWorkerStateStop) {
+            return UHFWorkerEventAborted;
+        }
+        if(raw_read_data->end) {
+            if(raw_read_data->data[1] == 0x01 && raw_read_data->data[5] == 0x15) {
+                uhf_data_reset(raw_read_data);
+                continue;
+            } else if(raw_read_data->data[1] == 0x02)
+                break; // read success
+        }
+    }
+
+    // todo : rfu ?
+    UHFTag* uhf_tag = uhf_worker->uhf_tag;
+    uhf_tag_reset(uhf_tag);
+
+    // add to tag object
+    UHFData* raw_bank_data = uhf_data_alloc();
+    size_t epc_length = (size_t)get_epc_length_in_bits(raw_read_data->data[6]) / 8;
+    size_t offset = (size_t)(8 + epc_length);
+
+    UHFData* read_bank_cmd = uhf_data_alloc();
+    read_bank_cmd->length = CMD_READ_LABEL_DATA_STORAGE.length;
+    memcpy(
+        (void*)&read_bank_cmd->data[0],
+        (void*)&CMD_READ_LABEL_DATA_STORAGE.cmd[0],
+        read_bank_cmd->length);
+
+    if(!send_set_select_command(raw_read_data, EPC_BANK)) return UHFWorkerEventFail;
+
+    int retry = 3;
+    do {
+        if(read_bank(read_bank_cmd, raw_bank_data, EPC_BANK)) {
+            uhf_tag_set_epc(uhf_tag, raw_bank_data->data + offset, epc_length + 2);
+            FURI_LOG_E("TAG", "epc read");
+            break;
+        }
+
+    } while(retry--);
+    // // debug
+    // furi_string_reset(temp_str);
+    // for(size_t i = 0; i < raw_bank_data->length; i++) {
+    //     furi_string_cat_printf(temp_str, "%02x ", raw_bank_data->data[i]);
+    // }
+    // FURI_LOG_E("TAG", "data = %s", furi_string_get_cstr(temp_str));
+    // // e-debug
+    uhf_data_reset(raw_bank_data);
+    retry = 3;
+    do {
+        if(read_bank(read_bank_cmd, raw_bank_data, TID_BANK)) {
+            uhf_tag_set_tid(uhf_tag, raw_bank_data->data + offset, 16);
+            break;
+        }
+    } while(retry--);
+    // // debug
+    // furi_string_reset(temp_str);
+    // for(size_t i = 0; i < raw_bank_data->length; i++) {
+    //     furi_string_cat_printf(temp_str, "%02x ", raw_bank_data->data[i]);
+    // }
+    // FURI_LOG_E("TAG", "data = %s", furi_string_get_cstr(temp_str));
+    // // e-debug
+    uhf_data_reset(raw_bank_data);
+    retry = 3;
+    if(raw_read_data->data[6] & 0x04) {
+        do {
+            if(read_bank(read_bank_cmd, raw_bank_data, USER_BANK)) {
+                uhf_tag_set_user(uhf_tag, raw_bank_data->data + offset, 16);
+                break;
+            }
+        } while(retry--);
+    }
+    // // debug
+    // furi_string_reset(temp_str);
+    // for(size_t i = 0; i < raw_bank_data->length; i++) {
+    //     furi_string_cat_printf(temp_str, "%02x ", raw_bank_data->data[i]);
+    // }
+    // FURI_LOG_E("TAG", "data = %s", furi_string_get_cstr(temp_str));
+    // // e-debug
+    uhf_data_reset(raw_bank_data);
+    uhf_data_free(raw_bank_data);
+    uhf_data_free(read_bank_cmd);
+    // debug
+    // furi_string_free(temp_str);
+    // e-debug
+
+    return UHFWorkerEventSuccess;
+}
+
+int32_t uhf_worker_task(void* ctx) {
+    UHFWorker* uhf_worker = ctx;
+    if(uhf_worker->state == UHFWorkerStateVerify) {
+        UHFWorkerEvent event = verify_module_connected(uhf_worker);
+        uhf_worker->callback(event, uhf_worker->ctx);
+    }
+    if(uhf_worker->state == UHFWorkerStateDetectSingle) {
+        UHFWorkerEvent event = read_single_card(uhf_worker);
+        uhf_worker->callback(event, uhf_worker->ctx);
+    }
+    return 0;
+}
+
+UHFWorker* uhf_worker_alloc() {
+    UHFWorker* uhf_worker = (UHFWorker*)malloc(sizeof(UHFWorker));
+    uhf_worker->thread = furi_thread_alloc_ex("UHFWorker", 8 * 1024, uhf_worker_task, uhf_worker);
+    uhf_worker->response_data = uhf_response_data_alloc();
+    uhf_worker->callback = NULL;
+    uhf_worker->ctx = NULL;
+    return uhf_worker;
+}
+
+void uhf_worker_change_state(UHFWorker* worker, UHFWorkerState state) {
+    worker->state = state;
+}
+
+void uhf_worker_start(
+    UHFWorker* uhf_worker,
+    UHFWorkerState state,
+    UHFWorkerCallback callback,
+    void* ctx) {
+    uhf_worker->state = state;
+    uhf_worker->callback = callback;
+    uhf_worker->ctx = ctx;
+    furi_thread_start(uhf_worker->thread);
+}
+
+void uhf_worker_stop(UHFWorker* uhf_worker) {
+    furi_assert(uhf_worker);
+    furi_assert(uhf_worker->thread);
+
+    if(furi_thread_get_state(uhf_worker->thread) != FuriThreadStateStopped) {
+        uhf_worker_change_state(uhf_worker, UHFWorkerStateStop);
+        furi_thread_join(uhf_worker->thread);
+    }
+}
+
+void uhf_worker_free(UHFWorker* uhf_worker) {
+    furi_assert(uhf_worker);
+    furi_thread_free(uhf_worker->thread);
+    uhf_response_data_free(uhf_worker->response_data);
+    free(uhf_worker);
+}

+ 49 - 0
non_catalog_apps/uhf_rfid/uhf_worker.h

@@ -0,0 +1,49 @@
+#pragma once
+
+#include <furi.h>
+#include <furi_hal.h>
+#include "uhf_data.h"
+
+typedef enum {
+    // Init states
+    UHFWorkerStateNone,
+    UHFWorkerStateBroken,
+    UHFWorkerStateReady,
+    UHFWorkerStateVerify,
+    // Main worker states
+    UHFWorkerStateDetectSingle,
+    UHFWorkerStateWriteSingle,
+    UHFWorkerStateWriteKey,
+    // Transition
+    UHFWorkerStateStop,
+} UHFWorkerState;
+
+typedef enum {
+    UHFWorkerEventSuccess,
+    UHFWorkerEventFail,
+    UHFWorkerEventNoTagDetected,
+    UHFWorkerEventAborted,
+    UHFWorkerEventCardDetected,
+} UHFWorkerEvent;
+
+typedef void (*UHFWorkerCallback)(UHFWorkerEvent event, void* ctx);
+
+typedef struct UHFWorker {
+    FuriThread* thread;
+    UHFResponseData* response_data;
+    UHFTag* uhf_tag;
+    UHFWorkerCallback callback;
+    UHFWorkerState state;
+    void* ctx;
+} UHFWorker;
+
+int32_t uhf_worker_task(void* ctx);
+UHFWorker* uhf_worker_alloc();
+void uhf_worker_change_state(UHFWorker* worker, UHFWorkerState state);
+void uhf_worker_start(
+    UHFWorker* uhf_worker,
+    UHFWorkerState state,
+    UHFWorkerCallback callback,
+    void* ctx);
+void uhf_worker_stop(UHFWorker* uhf_worker);
+void uhf_worker_free(UHFWorker* uhf_worker);