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

[FL-1371] New LF-RFID app. Second encounter. (#547)

* File worker: file operations helper.
* Notification app: removed yield
* File worker: write operations, calls to system file widgets
* App ibutton: use file worker
* Lfrfid: generic key loading, add path helper and hex conversion to lib
* FileWorker: plain C verison
* FileWorker: add to lib.mk
* FileWorker: add to C sources, instead of CPP
* Lfrfid: save scene
* App lfrfid: add key scene, saved key menu
* App lfrfid: saved key info scene
* App lfrfid: delete key scene
SG 4 лет назад
Родитель
Сommit
7a13391b2b
44 измененных файлов с 1653 добавлено и 462 удалено
  1. 5 5
      applications/ibutton/ibutton-app.cpp
  2. 17 0
      applications/lfrfid/helpers/key-info.cpp
  3. 1 0
      applications/lfrfid/helpers/key-info.h
  4. 28 7
      applications/lfrfid/helpers/rfid-key.cpp
  5. 6 4
      applications/lfrfid/helpers/rfid-key.h
  6. 2 2
      applications/lfrfid/helpers/rfid-writer.cpp
  7. 2 2
      applications/lfrfid/helpers/rfid-writer.h
  8. 2 2
      applications/lfrfid/lfrfid-app-launcher.cpp
  9. 166 10
      applications/lfrfid/lfrfid-app.cpp
  10. 22 1
      applications/lfrfid/lfrfid-app.h
  11. 99 0
      applications/lfrfid/scene/lfrfid-app-scene-delete-confirm.cpp
  12. 17 0
      applications/lfrfid/scene/lfrfid-app-scene-delete-confirm.h
  13. 38 0
      applications/lfrfid/scene/lfrfid-app-scene-delete-success.cpp
  14. 12 0
      applications/lfrfid/scene/lfrfid-app-scene-delete-success.h
  15. 7 3
      applications/lfrfid/scene/lfrfid-app-scene-emulate.cpp
  16. 1 1
      applications/lfrfid/scene/lfrfid-app-scene-read-success.cpp
  17. 58 0
      applications/lfrfid/scene/lfrfid-app-scene-save-data.cpp
  18. 33 0
      applications/lfrfid/scene/lfrfid-app-scene-save-data.h
  19. 12 8
      applications/lfrfid/scene/lfrfid-app-scene-save-name.cpp
  20. 46 0
      applications/lfrfid/scene/lfrfid-app-scene-save-success.cpp
  21. 12 0
      applications/lfrfid/scene/lfrfid-app-scene-save-success.h
  22. 46 0
      applications/lfrfid/scene/lfrfid-app-scene-save-type.cpp
  23. 13 0
      applications/lfrfid/scene/lfrfid-app-scene-save-type.h
  24. 77 0
      applications/lfrfid/scene/lfrfid-app-scene-saved-info.cpp
  25. 15 0
      applications/lfrfid/scene/lfrfid-app-scene-saved-info.h
  26. 67 0
      applications/lfrfid/scene/lfrfid-app-scene-saved-key-menu.cpp
  27. 13 0
      applications/lfrfid/scene/lfrfid-app-scene-saved-key-menu.h
  28. 18 0
      applications/lfrfid/scene/lfrfid-app-scene-select-key.cpp
  29. 9 0
      applications/lfrfid/scene/lfrfid-app-scene-select-key.h
  30. 6 0
      applications/lfrfid/scene/lfrfid-app-scene-start.cpp
  31. 7 3
      applications/lfrfid/scene/lfrfid-app-scene-write.cpp
  32. 68 0
      lib/app-scened-template/file-worker-cpp.cpp
  33. 135 0
      lib/app-scened-template/file-worker-cpp.h
  34. 280 0
      lib/app-scened-template/file-worker.c
  35. 0 237
      lib/app-scened-template/file-worker.cpp
  36. 163 144
      lib/app-scened-template/file-worker.h
  37. 72 7
      lib/app-scened-template/scene-controller.hpp
  38. 3 17
      lib/args/args.c
  39. 0 9
      lib/args/args.h
  40. 16 0
      lib/args/hex.c
  41. 20 0
      lib/args/hex.h
  42. 20 0
      lib/args/path.c
  43. 18 0
      lib/args/path.h
  44. 1 0
      lib/lib.mk

+ 5 - 5
applications/ibutton/ibutton-app.cpp

@@ -2,7 +2,7 @@
 #include <stdarg.h>
 #include <callback-connector.h>
 #include <m-string.h>
-#include <file-worker.h>
+#include <file-worker-cpp.h>
 
 const char* iButtonApp::app_folder = "ibutton";
 const char* iButtonApp::app_extension = ".ibtn";
@@ -217,7 +217,7 @@ void iButtonApp::generate_random_name(char* name, uint8_t max_name_size) {
 
 // file managment
 bool iButtonApp::save_key(const char* key_name) {
-    FileWorker file_worker;
+    FileWorkerCpp file_worker;
     string_t key_file_name;
     bool result = false;
 
@@ -274,7 +274,7 @@ bool iButtonApp::save_key(const char* key_name) {
 }
 
 bool iButtonApp::load_key_data(string_t key_path) {
-    FileWorker file_worker;
+    FileWorkerCpp file_worker;
 
     // Open key file
     if(!file_worker.open(string_get_cstr(key_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
@@ -344,7 +344,7 @@ bool iButtonApp::load_key(const char* key_name) {
 
 bool iButtonApp::load_key() {
     bool result = false;
-    FileWorker file_worker;
+    FileWorkerCpp file_worker;
 
     // Input events and views are managed by file_select
     bool res = file_worker.file_select(
@@ -369,7 +369,7 @@ bool iButtonApp::load_key() {
 bool iButtonApp::delete_key() {
     string_t file_name;
     bool result = false;
-    FileWorker file_worker;
+    FileWorkerCpp file_worker;
 
     string_init_printf(file_name, "%s/%s%s", app_folder, get_key()->get_name(), app_extension);
     result = file_worker.remove(string_get_cstr(file_name));

+ 17 - 0
applications/lfrfid/helpers/key-info.cpp

@@ -1,4 +1,5 @@
 #include "key-info.h"
+#include <string.h>
 
 const char* lfrfid_key_get_type_string(LfrfidKeyType type) {
     switch(type) {
@@ -16,6 +17,22 @@ const char* lfrfid_key_get_type_string(LfrfidKeyType type) {
     return "Unknown";
 }
 
+bool lfrfid_key_get_string_type(const char* string, LfrfidKeyType* type) {
+    bool result = true;
+
+    if(strcmp("EM4100", string) == 0) {
+        *type = LfrfidKeyType::KeyEM4100;
+    } else if(strcmp("H10301", string) == 0) {
+        *type = LfrfidKeyType::KeyH10301;
+    } else if(strcmp("I40134", string) == 0) {
+        *type = LfrfidKeyType::KeyI40134;
+    } else {
+        result = false;
+    }
+
+    return result;
+}
+
 uint8_t lfrfid_key_get_type_data_count(LfrfidKeyType type) {
     switch(type) {
     case LfrfidKeyType::KeyEM4100:

+ 1 - 0
applications/lfrfid/helpers/key-info.h

@@ -11,4 +11,5 @@ enum class LfrfidKeyType : uint8_t {
 };
 
 const char* lfrfid_key_get_type_string(LfrfidKeyType type);
+bool lfrfid_key_get_string_type(const char* string, LfrfidKeyType* type);
 uint8_t lfrfid_key_get_type_data_count(LfrfidKeyType type);

+ 28 - 7
applications/lfrfid/helpers/rfid-key.cpp

@@ -1,12 +1,9 @@
 #include "rfid-key.h"
 #include <furi/check.h>
+#include <string.h>
 
 RfidKey::RfidKey() {
-    data.fill(0);
-
-    for(uint8_t i = 0; i < (LFRFID_KEY_NAME_SIZE + 1); i++) {
-        name[i] = 0;
-    }
+    clear();
 }
 
 RfidKey::~RfidKey() {
@@ -16,18 +13,22 @@ void RfidKey::set_type(LfrfidKeyType _type) {
     type = _type;
 }
 
-void RfidKey::set_data(uint8_t* _data, const uint8_t _data_size) {
+void RfidKey::set_data(const uint8_t* _data, const uint8_t _data_size) {
     furi_assert(_data_size <= data.size());
     for(uint8_t i = 0; i < _data_size; i++) {
         data[i] = _data[i];
     }
 }
 
+void RfidKey::set_name(const char* _name) {
+    strlcpy(name, _name, get_name_length());
+}
+
 LfrfidKeyType RfidKey::get_type() {
     return type;
 }
 
-uint8_t* RfidKey::get_data() {
+const uint8_t* RfidKey::get_data() {
     return &data[0];
 }
 
@@ -42,3 +43,23 @@ const uint8_t RfidKey::get_type_data_count() {
 char* RfidKey::get_name() {
     return name;
 }
+
+uint8_t RfidKey::get_name_length() {
+    return LFRFID_KEY_NAME_SIZE;
+}
+
+void RfidKey::clear() {
+    set_name("");
+    set_type(LfrfidKeyType::KeyEM4100);
+    data.fill(0);
+}
+
+RfidKey& RfidKey::operator=(const RfidKey& rhs) {
+    if(this == &rhs) return *this;
+
+    set_type(rhs.type);
+    set_name(rhs.name);
+    set_data(&rhs.data[0], get_type_data_count());
+
+    return *this;
+}

+ 6 - 4
applications/lfrfid/helpers/rfid-key.h

@@ -8,15 +8,17 @@ public:
     ~RfidKey();
 
     void set_type(LfrfidKeyType type);
-    void set_data(uint8_t* data, const uint8_t data_size);
+    void set_data(const uint8_t* data, const uint8_t data_size);
+    void set_name(const char* name);
 
     LfrfidKeyType get_type();
-    uint8_t* get_data();
-
+    const uint8_t* get_data();
     const char* get_type_text();
     const uint8_t get_type_data_count();
-
     char* get_name();
+    uint8_t get_name_length();
+    void clear();
+    RfidKey& operator=(const RfidKey& rhs);
 
 private:
     std::array<uint8_t, LFRFID_KEY_SIZE> data;

+ 2 - 2
applications/lfrfid/helpers/rfid-writer.cpp

@@ -111,7 +111,7 @@ void RfidWriter::write_reset() {
     write_bit(0);
 }
 
-void RfidWriter::write_em(uint8_t em_data[5]) {
+void RfidWriter::write_em(const uint8_t em_data[5]) {
     ProtocolEMMarin em_card;
     uint64_t em_encoded_data;
     em_card.encode(em_data, 5, reinterpret_cast<uint8_t*>(&em_encoded_data), sizeof(uint64_t));
@@ -125,7 +125,7 @@ void RfidWriter::write_em(uint8_t em_data[5]) {
     __enable_irq();
 }
 
-void RfidWriter::write_hid(uint8_t hid_data[3]) {
+void RfidWriter::write_hid(const uint8_t hid_data[3]) {
     ProtocolHID10301 hid_card;
     uint32_t card_data[3];
     hid_card.encode(hid_data, 3, reinterpret_cast<uint8_t*>(&card_data), sizeof(card_data) * 3);

+ 2 - 2
applications/lfrfid/helpers/rfid-writer.h

@@ -7,8 +7,8 @@ public:
     ~RfidWriter();
     void start();
     void stop();
-    void write_em(uint8_t em_data[5]);
-    void write_hid(uint8_t hid_data[3]);
+    void write_em(const uint8_t em_data[5]);
+    void write_hid(const uint8_t hid_data[3]);
 
 private:
     void write_gap(uint32_t gap_time);

+ 2 - 2
applications/lfrfid/lfrfid-app-launcher.cpp

@@ -1,9 +1,9 @@
 #include "lfrfid-app.h"
 
 // app enter function
-extern "C" int32_t lfrfid_app(void* p) {
+extern "C" int32_t lfrfid_app(void* args) {
     LfRfidApp* app = new LfRfidApp();
-    app->run();
+    app->run(args);
     delete app;
 
     return 0;

+ 166 - 10
applications/lfrfid/lfrfid-app.cpp

@@ -7,6 +7,20 @@
 #include "scene/lfrfid-app-scene-write-success.h"
 #include "scene/lfrfid-app-scene-emulate.h"
 #include "scene/lfrfid-app-scene-save-name.h"
+#include "scene/lfrfid-app-scene-save-success.h"
+#include "scene/lfrfid-app-scene-select-key.h"
+#include "scene/lfrfid-app-scene-saved-key-menu.h"
+#include "scene/lfrfid-app-scene-save-data.h"
+#include "scene/lfrfid-app-scene-save-type.h"
+#include "scene/lfrfid-app-scene-saved-info.h"
+#include "scene/lfrfid-app-scene-delete-confirm.h"
+#include "scene/lfrfid-app-scene-delete-success.h"
+
+#include <file-worker-cpp.h>
+#include <path.h>
+
+const char* LfRfidApp::app_folder = "lfrfid";
+const char* LfRfidApp::app_extension = ".rfid";
 
 LfRfidApp::LfRfidApp()
     : scene_controller{this}
@@ -24,14 +38,156 @@ LfRfidApp::~LfRfidApp() {
     api_hal_power_insomnia_exit();
 }
 
-void LfRfidApp::run() {
-    scene_controller.add_scene(SceneType::Start, new LfRfidAppSceneStart());
-    scene_controller.add_scene(SceneType::Read, new LfRfidAppSceneRead());
-    scene_controller.add_scene(SceneType::ReadSuccess, new LfRfidAppSceneReadSuccess());
-    scene_controller.add_scene(SceneType::ReadedMenu, new LfRfidAppSceneReadedMenu());
-    scene_controller.add_scene(SceneType::Write, new LfRfidAppSceneWrite());
-    scene_controller.add_scene(SceneType::WriteSuccess, new LfRfidAppSceneWriteSuccess());
-    scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate());
-    scene_controller.add_scene(SceneType::SaveName, new LfRfidAppSceneSaveName());
-    scene_controller.process(100);
+void LfRfidApp::run(void* _args) {
+    const char* args = reinterpret_cast<const char*>(_args);
+
+    if(strlen(args)) {
+        load_key_data(args, &worker.key);
+        scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate());
+        scene_controller.process(100, SceneType::Emulate);
+    } else {
+        scene_controller.add_scene(SceneType::Start, new LfRfidAppSceneStart());
+        scene_controller.add_scene(SceneType::Read, new LfRfidAppSceneRead());
+        scene_controller.add_scene(SceneType::ReadSuccess, new LfRfidAppSceneReadSuccess());
+        scene_controller.add_scene(SceneType::ReadedMenu, new LfRfidAppSceneReadedMenu());
+        scene_controller.add_scene(SceneType::Write, new LfRfidAppSceneWrite());
+        scene_controller.add_scene(SceneType::WriteSuccess, new LfRfidAppSceneWriteSuccess());
+        scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate());
+        scene_controller.add_scene(SceneType::SaveName, new LfRfidAppSceneSaveName());
+        scene_controller.add_scene(SceneType::SaveSuccess, new LfRfidAppSceneSaveSuccess());
+        scene_controller.add_scene(SceneType::SelectKey, new LfRfidAppSceneSelectKey());
+        scene_controller.add_scene(SceneType::SavedKeyMenu, new LfRfidAppSceneSavedKeyMenu());
+        scene_controller.add_scene(SceneType::SaveData, new LfRfidAppSceneSaveData());
+        scene_controller.add_scene(SceneType::SaveType, new LfRfidAppSceneSaveType());
+        scene_controller.add_scene(SceneType::SavedInfo, new LfRfidAppSceneSavedInfo());
+        scene_controller.add_scene(SceneType::DeleteConfirm, new LfRfidAppSceneDeleteConfirm());
+        scene_controller.add_scene(SceneType::DeleteSuccess, new LfRfidAppSceneDeleteSuccess());
+        scene_controller.process(100);
+    }
+}
+
+bool LfRfidApp::save_key(RfidKey* key) {
+    string_t file_name;
+    bool result = false;
+
+    make_app_folder();
+
+    string_init_printf(file_name, "%s/%s%s", app_folder, key->get_name(), app_extension);
+    result = save_key_data(string_get_cstr(file_name), key);
+    string_clear(file_name);
+
+    return result;
+}
+
+bool LfRfidApp::load_key_from_file_select(bool need_restore) {
+    FileWorkerCpp file_worker;
+    TextStore* filename_ts = new TextStore(64);
+    bool result;
+
+    if(need_restore) {
+        result = file_worker.file_select(
+            app_folder,
+            app_extension,
+            filename_ts->text,
+            filename_ts->text_size,
+            worker.key.get_name());
+    } else {
+        result = file_worker.file_select(
+            app_folder, app_extension, filename_ts->text, filename_ts->text_size, NULL);
+    }
+
+    if(result) {
+        string_t key_str;
+        string_init_printf(key_str, "%s/%s%s", app_folder, filename_ts->text, app_extension);
+        result = load_key_data(string_get_cstr(key_str), &worker.key);
+        string_clear(key_str);
+    }
+
+    delete filename_ts;
+    return result;
+}
+
+bool LfRfidApp::delete_key(RfidKey* key) {
+    FileWorkerCpp file_worker;
+    string_t file_name;
+    bool result = false;
+
+    string_init_printf(file_name, "%s/%s%s", app_folder, key->get_name(), app_extension);
+    result = file_worker.remove(string_get_cstr(file_name));
+    string_clear(file_name);
+
+    return result;
+}
+
+bool LfRfidApp::load_key_data(const char* path, RfidKey* key) {
+    FileWorkerCpp file_worker;
+    bool result = false;
+
+    bool res = file_worker.open(path, FSAM_READ, FSOM_OPEN_EXISTING);
+
+    if(res) {
+        string_t str_result;
+        string_init(str_result);
+
+        do {
+            RfidKey loaded_key;
+            LfrfidKeyType loaded_type;
+
+            // load type
+            if(!file_worker.read_until(str_result, ' ')) break;
+            if(!lfrfid_key_get_string_type(string_get_cstr(str_result), &loaded_type)) {
+                file_worker.show_error("Cannot parse\nfile");
+                break;
+            }
+            loaded_key.set_type(loaded_type);
+
+            // load data
+            uint8_t tmp_data[loaded_key.get_type_data_count()];
+            if(!file_worker.read_hex(tmp_data, loaded_key.get_type_data_count())) break;
+            loaded_key.set_data(tmp_data, loaded_key.get_type_data_count());
+
+            *key = loaded_key;
+            result = true;
+        } while(0);
+
+        // load name
+        path_extract_filename_no_ext(path, str_result);
+        key->set_name(string_get_cstr(str_result));
+
+        string_clear(str_result);
+    }
+
+    file_worker.close();
+
+    return result;
+}
+
+bool LfRfidApp::save_key_data(const char* path, RfidKey* key) {
+    FileWorkerCpp file_worker;
+    bool result = false;
+
+    bool res = file_worker.open(path, FSAM_WRITE, FSOM_CREATE_ALWAYS);
+
+    if(res) {
+        do {
+            // type header
+            const char* key_type = lfrfid_key_get_type_string(key->get_type());
+            char delimeter = ' ';
+
+            if(!file_worker.write(key_type, strlen(key_type))) break;
+            if(!file_worker.write(&delimeter)) break;
+            if(!file_worker.write_hex(key->get_data(), key->get_type_data_count())) break;
+
+            result = true;
+        } while(0);
+    }
+
+    file_worker.close();
+
+    return result;
+}
+
+void LfRfidApp::make_app_folder() {
+    FileWorkerCpp file_worker;
+    file_worker.mkdir(app_folder);
 }

+ 22 - 1
applications/lfrfid/lfrfid-app.h

@@ -38,6 +38,14 @@ public:
         WriteSuccess,
         Emulate,
         SaveName,
+        SaveSuccess,
+        SelectKey,
+        SavedKeyMenu,
+        SaveData,
+        SaveType,
+        SavedInfo,
+        DeleteConfirm,
+        DeleteSuccess,
     };
 
     class Event {
@@ -63,5 +71,18 @@ public:
     RfidWorker worker;
 
     TextStore text_store;
-    void run();
+
+    void run(void* args);
+
+    static const char* app_folder;
+    static const char* app_extension;
+
+    bool save_key(RfidKey* key);
+    bool load_key_from_file_select(bool need_restore);
+    bool delete_key(RfidKey* key);
+
+    bool load_key_data(const char* path, RfidKey* key);
+    bool save_key_data(const char* path, RfidKey* key);
+
+    void make_app_folder();
 };

+ 99 - 0
applications/lfrfid/scene/lfrfid-app-scene-delete-confirm.cpp

@@ -0,0 +1,99 @@
+#include "lfrfid-app-scene-delete-confirm.h"
+#include "../view/elements/button-element.h"
+#include "../view/elements/icon-element.h"
+#include "../view/elements/string-element.h"
+
+void LfRfidAppSceneDeleteConfirm::on_enter(LfRfidApp* app, bool need_restore) {
+    string_init(string_data);
+    string_init(string_decrypted);
+    string_init(string_header);
+
+    auto container = app->view_controller.get<ContainerVM>();
+
+    auto button = container->add<ButtonElement>();
+    button->set_type(ButtonElement::Type::Left, "Back");
+    button->set_callback(app, LfRfidAppSceneDeleteConfirm::back_callback);
+
+    button = container->add<ButtonElement>();
+    button->set_type(ButtonElement::Type::Right, "Delete");
+    button->set_callback(app, LfRfidAppSceneDeleteConfirm::delete_callback);
+
+    auto line_1 = container->add<StringElement>();
+    auto line_2 = container->add<StringElement>();
+    auto line_3 = container->add<StringElement>();
+    auto line_4 = container->add<StringElement>();
+
+    RfidKey& key = app->worker.key;
+    const uint8_t* data = key.get_data();
+
+    for(uint8_t i = 0; i < key.get_type_data_count(); i++) {
+        if(i != 0) {
+            string_cat_printf(string_data, " ");
+        }
+        string_cat_printf(string_data, "%02X", data[i]);
+    }
+
+    string_printf(string_header, "Delete %s?", key.get_name());
+    line_1->set_text(
+        string_get_cstr(string_header), 64, 19, AlignCenter, AlignBottom, FontPrimary);
+    line_2->set_text(
+        string_get_cstr(string_data), 64, 29, AlignCenter, AlignBottom, FontSecondary);
+
+    switch(key.get_type()) {
+    case LfrfidKeyType::KeyEM4100:
+        string_printf(
+            string_decrypted, "%03u,%05u", data[2], (uint16_t)((data[3] << 8) | (data[4])));
+
+        break;
+    case LfrfidKeyType::KeyH10301:
+    case LfrfidKeyType::KeyI40134:
+        string_printf(
+            string_decrypted, "FC: %u    ID: %u", data[0], (uint16_t)((data[1] << 8) | (data[2])));
+        break;
+    }
+    line_3->set_text(
+        string_get_cstr(string_decrypted), 64, 39, AlignCenter, AlignBottom, FontSecondary);
+
+    line_4->set_text(
+        lfrfid_key_get_type_string(key.get_type()),
+        64,
+        49,
+        AlignCenter,
+        AlignBottom,
+        FontSecondary);
+
+    app->view_controller.switch_to<ContainerVM>();
+}
+
+bool LfRfidAppSceneDeleteConfirm::on_event(LfRfidApp* app, LfRfidApp::Event* event) {
+    bool consumed = false;
+
+    if(event->type == LfRfidApp::EventType::Next) {
+        app->delete_key(&app->worker.key);
+        app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::DeleteSuccess);
+        consumed = true;
+    }
+
+    return consumed;
+}
+
+void LfRfidAppSceneDeleteConfirm::on_exit(LfRfidApp* app) {
+    app->view_controller.get<ContainerVM>()->clean();
+    string_clear(string_data);
+    string_clear(string_decrypted);
+    string_clear(string_header);
+}
+
+void LfRfidAppSceneDeleteConfirm::back_callback(void* context) {
+    LfRfidApp* app = static_cast<LfRfidApp*>(context);
+    LfRfidApp::Event event;
+    event.type = LfRfidApp::EventType::Back;
+    app->view_controller.send_event(&event);
+}
+
+void LfRfidAppSceneDeleteConfirm::delete_callback(void* context) {
+    LfRfidApp* app = static_cast<LfRfidApp*>(context);
+    LfRfidApp::Event event;
+    event.type = LfRfidApp::EventType::Next;
+    app->view_controller.send_event(&event);
+}

+ 17 - 0
applications/lfrfid/scene/lfrfid-app-scene-delete-confirm.h

@@ -0,0 +1,17 @@
+#pragma once
+#include "../lfrfid-app.h"
+
+class LfRfidAppSceneDeleteConfirm : public GenericScene<LfRfidApp> {
+public:
+    void on_enter(LfRfidApp* app, bool need_restore) final;
+    bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final;
+    void on_exit(LfRfidApp* app) final;
+
+private:
+    static void back_callback(void* context);
+    static void delete_callback(void* context);
+
+    string_t string_header;
+    string_t string_data;
+    string_t string_decrypted;
+};

+ 38 - 0
applications/lfrfid/scene/lfrfid-app-scene-delete-success.cpp

@@ -0,0 +1,38 @@
+#include "lfrfid-app-scene-delete-success.h"
+
+void LfRfidAppSceneDeleteSuccess::on_enter(LfRfidApp* app, bool need_restore) {
+    auto popup = app->view_controller.get<PopupVM>();
+
+    popup->set_icon(0, 2, I_DolphinMafia_115x62);
+    popup->set_text("Deleted", 83, 19, AlignLeft, AlignBottom);
+    popup->set_context(app);
+    popup->set_callback(LfRfidAppSceneDeleteSuccess::timeout_callback);
+    popup->set_timeout(1500);
+    popup->enable_timeout();
+
+    app->view_controller.switch_to<PopupVM>();
+}
+
+bool LfRfidAppSceneDeleteSuccess::on_event(LfRfidApp* app, LfRfidApp::Event* event) {
+    bool consumed = false;
+
+    if(event->type == LfRfidApp::EventType::Back) {
+        app->scene_controller.search_and_switch_to_previous_scene(
+            {LfRfidApp::SceneType::SelectKey});
+
+        consumed = true;
+    }
+
+    return consumed;
+}
+
+void LfRfidAppSceneDeleteSuccess::on_exit(LfRfidApp* app) {
+    app->view_controller.get<PopupVM>()->clean();
+}
+
+void LfRfidAppSceneDeleteSuccess::timeout_callback(void* context) {
+    LfRfidApp* app = static_cast<LfRfidApp*>(context);
+    LfRfidApp::Event event;
+    event.type = LfRfidApp::EventType::Back;
+    app->view_controller.send_event(&event);
+}

+ 12 - 0
applications/lfrfid/scene/lfrfid-app-scene-delete-success.h

@@ -0,0 +1,12 @@
+#pragma once
+#include "../lfrfid-app.h"
+
+class LfRfidAppSceneDeleteSuccess : public GenericScene<LfRfidApp> {
+public:
+    void on_enter(LfRfidApp* app, bool need_restore) final;
+    bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final;
+    void on_exit(LfRfidApp* app) final;
+
+private:
+    static void timeout_callback(void* context);
+};

+ 7 - 3
applications/lfrfid/scene/lfrfid-app-scene-emulate.cpp

@@ -3,7 +3,7 @@
 void LfRfidAppSceneEmulate::on_enter(LfRfidApp* app, bool need_restore) {
     string_init(data_string);
 
-    uint8_t* data = app->worker.key.get_data();
+    const uint8_t* data = app->worker.key.get_data();
 
     for(uint8_t i = 0; i < app->worker.key.get_type_data_count(); i++) {
         string_cat_printf(data_string, "%02X", data[i]);
@@ -11,8 +11,12 @@ void LfRfidAppSceneEmulate::on_enter(LfRfidApp* app, bool need_restore) {
 
     auto popup = app->view_controller.get<PopupVM>();
 
-    popup->set_header("Emulating", 90, 34, AlignCenter, AlignTop);
-    popup->set_text(string_get_cstr(data_string), 90, 48, AlignCenter, AlignTop);
+    popup->set_header("LF emulating", 89, 30, AlignCenter, AlignTop);
+    if(strlen(app->worker.key.get_name())) {
+        popup->set_text(app->worker.key.get_name(), 89, 43, AlignCenter, AlignTop);
+    } else {
+        popup->set_text(string_get_cstr(data_string), 89, 43, AlignCenter, AlignTop);
+    }
     popup->set_icon(0, 4, I_RFIDDolphinSend_98x60);
 
     app->view_controller.switch_to<PopupVM>();

+ 1 - 1
applications/lfrfid/scene/lfrfid-app-scene-read-success.cpp

@@ -32,7 +32,7 @@ void LfRfidAppSceneReadSuccess::on_enter(LfRfidApp* app, bool need_restore) {
     auto line_2_value = container->add<StringElement>();
     auto line_3_value = container->add<StringElement>();
 
-    uint8_t* data = app->worker.key.get_data();
+    const uint8_t* data = app->worker.key.get_data();
 
     switch(app->worker.key.get_type()) {
     case LfrfidKeyType::KeyEM4100:

+ 58 - 0
applications/lfrfid/scene/lfrfid-app-scene-save-data.cpp

@@ -0,0 +1,58 @@
+#include "lfrfid-app-scene-save-data.h"
+
+static void print_buffer(const uint8_t* buffer) {
+    for(uint8_t i = 0; i < LFRFID_KEY_SIZE; i++) {
+        printf("%02X", buffer[i]);
+    }
+}
+
+void LfRfidAppSceneSaveData::on_enter(LfRfidApp* app, bool need_restore) {
+    auto byte_input = app->view_controller.get<ByteInputVM>();
+    RfidKey& key = app->worker.key;
+
+    printf("k: ");
+    print_buffer(key.get_data());
+    printf(" o: ");
+    print_buffer(old_key_data);
+    printf(" n: ");
+    print_buffer(new_key_data);
+    printf("\r\n");
+    if(need_restore) printf("restored\r\n");
+
+    if(need_restore) {
+        key.set_data(old_key_data, key.get_type_data_count());
+    } else {
+        memcpy(old_key_data, key.get_data(), key.get_type_data_count());
+    }
+
+    memcpy(new_key_data, key.get_data(), key.get_type_data_count());
+    byte_input->set_header_text("Enter the data in hex");
+
+    byte_input->set_result_callback(
+        save_callback, NULL, app, new_key_data, app->worker.key.get_type_data_count());
+
+    app->view_controller.switch_to<ByteInputVM>();
+}
+
+bool LfRfidAppSceneSaveData::on_event(LfRfidApp* app, LfRfidApp::Event* event) {
+    bool consumed = false;
+    RfidKey& key = app->worker.key;
+
+    if(event->type == LfRfidApp::EventType::Next) {
+        key.set_data(new_key_data, key.get_type_data_count());
+        app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::SaveName);
+    }
+
+    return consumed;
+}
+
+void LfRfidAppSceneSaveData::on_exit(LfRfidApp* app) {
+    app->view_controller.get<ByteInputVM>()->clean();
+}
+
+void LfRfidAppSceneSaveData::save_callback(void* context, uint8_t* bytes, uint8_t bytes_count) {
+    LfRfidApp* app = static_cast<LfRfidApp*>(context);
+    LfRfidApp::Event event;
+    event.type = LfRfidApp::EventType::Next;
+    app->view_controller.send_event(&event);
+}

+ 33 - 0
applications/lfrfid/scene/lfrfid-app-scene-save-data.h

@@ -0,0 +1,33 @@
+#pragma once
+#include "../lfrfid-app.h"
+
+class LfRfidAppSceneSaveData : public GenericScene<LfRfidApp> {
+public:
+    void on_enter(LfRfidApp* app, bool need_restore) final;
+    bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final;
+    void on_exit(LfRfidApp* app) final;
+
+private:
+    static void save_callback(void* context, uint8_t* bytes, uint8_t bytes_count);
+    uint8_t old_key_data[LFRFID_KEY_SIZE] = {
+        0xAA,
+        0xAA,
+        0xAA,
+        0xAA,
+        0xAA,
+        0xAA,
+        0xAA,
+        0xAA,
+    };
+
+    uint8_t new_key_data[LFRFID_KEY_SIZE] = {
+        0xBB,
+        0xBB,
+        0xBB,
+        0xBB,
+        0xBB,
+        0xBB,
+        0xBB,
+        0xBB,
+    };
+};

+ 12 - 8
applications/lfrfid/scene/lfrfid-app-scene-save-name.cpp

@@ -14,7 +14,7 @@ void LfRfidAppSceneSaveName::on_enter(LfRfidApp* app, bool need_restore) {
     text_input->set_header_text("Name the card");
 
     text_input->set_result_callback(
-        save_callback, app, app->text_store.text, LFRFID_KEY_NAME_SIZE);
+        save_callback, app, app->text_store.text, app->worker.key.get_name_length());
 
     app->view_controller.switch_to<TextInputVM>();
 }
@@ -23,14 +23,18 @@ bool LfRfidAppSceneSaveName::on_event(LfRfidApp* app, LfRfidApp::Event* event) {
     bool consumed = false;
 
     if(event->type == LfRfidApp::EventType::Next) {
-        /*if(app->save_key(app->get_text_store())) {
-            app->switch_to_next_scene(iButtonApp::Scene::SceneSaveSuccess);
+        if(strlen(app->worker.key.get_name())) {
+            app->delete_key(&app->worker.key);
+        }
+
+        app->worker.key.set_name(app->text_store.text);
+
+        if(app->save_key(&app->worker.key)) {
+            app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::SaveSuccess);
         } else {
-            app->search_and_switch_to_previous_scene(
-                {iButtonApp::Scene::SceneReadedKeyMenu,
-                 iButtonApp::Scene::SceneSavedKeyMenu,
-                 iButtonApp::Scene::SceneAddType});
-        }*/
+            app->scene_controller.search_and_switch_to_previous_scene(
+                {LfRfidApp::SceneType::ReadedMenu});
+        }
     }
 
     return consumed;

+ 46 - 0
applications/lfrfid/scene/lfrfid-app-scene-save-success.cpp

@@ -0,0 +1,46 @@
+#include "lfrfid-app-scene-save-success.h"
+
+void LfRfidAppSceneSaveSuccess::on_enter(LfRfidApp* app, bool need_restore) {
+    auto popup = app->view_controller.get<PopupVM>();
+
+    popup->set_icon(32, 5, I_DolphinNice_96x59);
+    popup->set_text("Saved!", 13, 22, AlignLeft, AlignBottom);
+    popup->set_context(app);
+    popup->set_callback(LfRfidAppSceneSaveSuccess::timeout_callback);
+    popup->set_timeout(1500);
+    popup->enable_timeout();
+
+    app->view_controller.switch_to<PopupVM>();
+}
+
+bool LfRfidAppSceneSaveSuccess::on_event(LfRfidApp* app, LfRfidApp::Event* event) {
+    bool consumed = false;
+
+    if(event->type == LfRfidApp::EventType::Back) {
+        bool result = app->scene_controller.has_previous_scene(
+            {LfRfidApp::SceneType::ReadedMenu, LfRfidApp::SceneType::SelectKey});
+
+        if(result) {
+            app->scene_controller.search_and_switch_to_previous_scene(
+                {LfRfidApp::SceneType::ReadedMenu, LfRfidApp::SceneType::SelectKey});
+        } else {
+            app->scene_controller.search_and_switch_to_another_scene(
+                {LfRfidApp::SceneType::SaveType}, LfRfidApp::SceneType::SelectKey);
+        }
+
+        consumed = true;
+    }
+
+    return consumed;
+}
+
+void LfRfidAppSceneSaveSuccess::on_exit(LfRfidApp* app) {
+    app->view_controller.get<PopupVM>()->clean();
+}
+
+void LfRfidAppSceneSaveSuccess::timeout_callback(void* context) {
+    LfRfidApp* app = static_cast<LfRfidApp*>(context);
+    LfRfidApp::Event event;
+    event.type = LfRfidApp::EventType::Back;
+    app->view_controller.send_event(&event);
+}

+ 12 - 0
applications/lfrfid/scene/lfrfid-app-scene-save-success.h

@@ -0,0 +1,12 @@
+#pragma once
+#include "../lfrfid-app.h"
+
+class LfRfidAppSceneSaveSuccess : public GenericScene<LfRfidApp> {
+public:
+    void on_enter(LfRfidApp* app, bool need_restore) final;
+    bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final;
+    void on_exit(LfRfidApp* app) final;
+
+private:
+    static void timeout_callback(void* context);
+};

+ 46 - 0
applications/lfrfid/scene/lfrfid-app-scene-save-type.cpp

@@ -0,0 +1,46 @@
+#include "lfrfid-app-scene-save-type.h"
+
+void LfRfidAppSceneSaveType::on_enter(LfRfidApp* app, bool need_restore) {
+    auto submenu = app->view_controller.get<SubmenuVM>();
+
+    for(uint8_t i = 0; i <= static_cast<uint8_t>(LfrfidKeyType::KeyI40134); i++) {
+        submenu->add_item(
+            lfrfid_key_get_type_string(static_cast<LfrfidKeyType>(i)), i, submenu_callback, app);
+    }
+
+    if(need_restore) {
+        submenu->set_selected_item(submenu_item_selected);
+    }
+
+    app->view_controller.switch_to<SubmenuVM>();
+
+    // clear key name
+    app->worker.key.set_name("");
+}
+
+bool LfRfidAppSceneSaveType::on_event(LfRfidApp* app, LfRfidApp::Event* event) {
+    bool consumed = false;
+
+    if(event->type == LfRfidApp::EventType::MenuSelected) {
+        submenu_item_selected = event->payload.menu_index;
+        app->worker.key.set_type(static_cast<LfrfidKeyType>(event->payload.menu_index));
+        app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::SaveData);
+        consumed = true;
+    }
+
+    return consumed;
+}
+
+void LfRfidAppSceneSaveType::on_exit(LfRfidApp* app) {
+    app->view_controller.get<SubmenuVM>()->clean();
+}
+
+void LfRfidAppSceneSaveType::submenu_callback(void* context, uint32_t index) {
+    LfRfidApp* app = static_cast<LfRfidApp*>(context);
+    LfRfidApp::Event event;
+
+    event.type = LfRfidApp::EventType::MenuSelected;
+    event.payload.menu_index = index;
+
+    app->view_controller.send_event(&event);
+}

+ 13 - 0
applications/lfrfid/scene/lfrfid-app-scene-save-type.h

@@ -0,0 +1,13 @@
+#pragma once
+#include "../lfrfid-app.h"
+
+class LfRfidAppSceneSaveType : public GenericScene<LfRfidApp> {
+public:
+    void on_enter(LfRfidApp* app, bool need_restore) final;
+    bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final;
+    void on_exit(LfRfidApp* app) final;
+
+private:
+    static void submenu_callback(void* context, uint32_t index);
+    uint32_t submenu_item_selected = 0;
+};

+ 77 - 0
applications/lfrfid/scene/lfrfid-app-scene-saved-info.cpp

@@ -0,0 +1,77 @@
+#include "lfrfid-app-scene-saved-info.h"
+#include "../view/elements/button-element.h"
+#include "../view/elements/icon-element.h"
+#include "../view/elements/string-element.h"
+
+void LfRfidAppSceneSavedInfo::on_enter(LfRfidApp* app, bool need_restore) {
+    string_init(string_data);
+    string_init(string_decrypted);
+
+    auto container = app->view_controller.get<ContainerVM>();
+
+    auto button = container->add<ButtonElement>();
+    button->set_type(ButtonElement::Type::Left, "Back");
+    button->set_callback(app, LfRfidAppSceneSavedInfo::back_callback);
+
+    auto line_1 = container->add<StringElement>();
+    auto line_2 = container->add<StringElement>();
+    auto line_3 = container->add<StringElement>();
+    auto line_4 = container->add<StringElement>();
+
+    RfidKey& key = app->worker.key;
+    const uint8_t* data = key.get_data();
+
+    for(uint8_t i = 0; i < key.get_type_data_count(); i++) {
+        if(i != 0) {
+            string_cat_printf(string_data, " ");
+        }
+        string_cat_printf(string_data, "%02X", data[i]);
+    }
+
+    line_1->set_text(key.get_name(), 64, 17, AlignCenter, AlignBottom, FontSecondary);
+    line_2->set_text(string_get_cstr(string_data), 64, 29, AlignCenter, AlignBottom, FontPrimary);
+
+    switch(key.get_type()) {
+    case LfrfidKeyType::KeyEM4100:
+        string_printf(
+            string_decrypted, "%03u,%05u", data[2], (uint16_t)((data[3] << 8) | (data[4])));
+
+        break;
+    case LfrfidKeyType::KeyH10301:
+    case LfrfidKeyType::KeyI40134:
+        string_printf(
+            string_decrypted, "FC: %u    ID: %u", data[0], (uint16_t)((data[1] << 8) | (data[2])));
+        break;
+    }
+    line_3->set_text(
+        string_get_cstr(string_decrypted), 64, 39, AlignCenter, AlignBottom, FontSecondary);
+
+    line_4->set_text(
+        lfrfid_key_get_type_string(key.get_type()),
+        64,
+        49,
+        AlignCenter,
+        AlignBottom,
+        FontSecondary);
+
+    app->view_controller.switch_to<ContainerVM>();
+}
+
+bool LfRfidAppSceneSavedInfo::on_event(LfRfidApp* app, LfRfidApp::Event* event) {
+    bool consumed = false;
+
+    return consumed;
+}
+
+void LfRfidAppSceneSavedInfo::on_exit(LfRfidApp* app) {
+    app->view_controller.get<ContainerVM>()->clean();
+    string_clear(string_data);
+    string_clear(string_decrypted);
+}
+
+void LfRfidAppSceneSavedInfo::back_callback(void* context) {
+    LfRfidApp* app = static_cast<LfRfidApp*>(context);
+    LfRfidApp::Event event;
+    event.type = LfRfidApp::EventType::Back;
+    app->view_controller.send_event(&event);
+}

+ 15 - 0
applications/lfrfid/scene/lfrfid-app-scene-saved-info.h

@@ -0,0 +1,15 @@
+#pragma once
+#include "../lfrfid-app.h"
+
+class LfRfidAppSceneSavedInfo : public GenericScene<LfRfidApp> {
+public:
+    void on_enter(LfRfidApp* app, bool need_restore) final;
+    bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final;
+    void on_exit(LfRfidApp* app) final;
+
+private:
+    static void back_callback(void* context);
+
+    string_t string_data;
+    string_t string_decrypted;
+};

+ 67 - 0
applications/lfrfid/scene/lfrfid-app-scene-saved-key-menu.cpp

@@ -0,0 +1,67 @@
+#include "lfrfid-app-scene-saved-key-menu.h"
+
+typedef enum {
+    SubmenuEmulate,
+    SubmenuWrite,
+    SubmenuEdit,
+    SubmenuDelete,
+    SubmenuInfo,
+} SubmenuIndex;
+
+void LfRfidAppSceneSavedKeyMenu::on_enter(LfRfidApp* app, bool need_restore) {
+    auto submenu = app->view_controller.get<SubmenuVM>();
+
+    submenu->add_item("Emulate", SubmenuEmulate, submenu_callback, app);
+    submenu->add_item("Write", SubmenuWrite, submenu_callback, app);
+    submenu->add_item("Edit", SubmenuEdit, submenu_callback, app);
+    submenu->add_item("Delete", SubmenuDelete, submenu_callback, app);
+    submenu->add_item("Info", SubmenuInfo, submenu_callback, app);
+
+    if(need_restore) {
+        submenu->set_selected_item(submenu_item_selected);
+    }
+
+    app->view_controller.switch_to<SubmenuVM>();
+}
+
+bool LfRfidAppSceneSavedKeyMenu::on_event(LfRfidApp* app, LfRfidApp::Event* event) {
+    bool consumed = false;
+
+    if(event->type == LfRfidApp::EventType::MenuSelected) {
+        submenu_item_selected = event->payload.menu_index;
+        switch(event->payload.menu_index) {
+        case SubmenuEmulate:
+            app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::Emulate);
+            break;
+        case SubmenuWrite:
+            app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::Write);
+            break;
+        case SubmenuEdit:
+            app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::SaveData);
+            break;
+        case SubmenuDelete:
+            app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::DeleteConfirm);
+            break;
+        case SubmenuInfo:
+            app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::SavedInfo);
+            break;
+        }
+        consumed = true;
+    }
+
+    return consumed;
+}
+
+void LfRfidAppSceneSavedKeyMenu::on_exit(LfRfidApp* app) {
+    app->view_controller.get<SubmenuVM>()->clean();
+}
+
+void LfRfidAppSceneSavedKeyMenu::submenu_callback(void* context, uint32_t index) {
+    LfRfidApp* app = static_cast<LfRfidApp*>(context);
+    LfRfidApp::Event event;
+
+    event.type = LfRfidApp::EventType::MenuSelected;
+    event.payload.menu_index = index;
+
+    app->view_controller.send_event(&event);
+}

+ 13 - 0
applications/lfrfid/scene/lfrfid-app-scene-saved-key-menu.h

@@ -0,0 +1,13 @@
+#pragma once
+#include "../lfrfid-app.h"
+
+class LfRfidAppSceneSavedKeyMenu : public GenericScene<LfRfidApp> {
+public:
+    void on_enter(LfRfidApp* app, bool need_restore) final;
+    bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final;
+    void on_exit(LfRfidApp* app) final;
+
+private:
+    static void submenu_callback(void* context, uint32_t index);
+    uint32_t submenu_item_selected = 0;
+};

+ 18 - 0
applications/lfrfid/scene/lfrfid-app-scene-select-key.cpp

@@ -0,0 +1,18 @@
+#include "lfrfid-app-scene-select-key.h"
+
+void LfRfidAppSceneSelectKey::on_enter(LfRfidApp* app, bool need_restore) {
+    if(app->load_key_from_file_select(need_restore)) {
+        app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::SavedKeyMenu);
+    } else {
+        app->scene_controller.switch_to_previous_scene();
+    }
+}
+
+bool LfRfidAppSceneSelectKey::on_event(LfRfidApp* app, LfRfidApp::Event* event) {
+    bool consumed = false;
+
+    return consumed;
+}
+
+void LfRfidAppSceneSelectKey::on_exit(LfRfidApp* app) {
+}

+ 9 - 0
applications/lfrfid/scene/lfrfid-app-scene-select-key.h

@@ -0,0 +1,9 @@
+#pragma once
+#include "../lfrfid-app.h"
+
+class LfRfidAppSceneSelectKey : public GenericScene<LfRfidApp> {
+public:
+    void on_enter(LfRfidApp* app, bool need_restore) final;
+    bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final;
+    void on_exit(LfRfidApp* app) final;
+};

+ 6 - 0
applications/lfrfid/scene/lfrfid-app-scene-start.cpp

@@ -16,7 +16,11 @@ void LfRfidAppSceneStart::on_enter(LfRfidApp* app, bool need_restore) {
     if(need_restore) {
         submenu->set_selected_item(submenu_item_selected);
     }
+
     app->view_controller.switch_to<SubmenuVM>();
+
+    // clear key
+    app->worker.key.clear();
 }
 
 bool LfRfidAppSceneStart::on_event(LfRfidApp* app, LfRfidApp::Event* event) {
@@ -29,8 +33,10 @@ bool LfRfidAppSceneStart::on_event(LfRfidApp* app, LfRfidApp::Event* event) {
             app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::Read);
             break;
         case SubmenuSaved:
+            app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::SelectKey);
             break;
         case SubmenuAddManually:
+            app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::SaveType);
             break;
         }
         consumed = true;

+ 7 - 3
applications/lfrfid/scene/lfrfid-app-scene-write.cpp

@@ -4,7 +4,7 @@ void LfRfidAppSceneWrite::on_enter(LfRfidApp* app, bool need_restore) {
     card_not_supported = false;
     string_init(data_string);
 
-    uint8_t* data = app->worker.key.get_data();
+    const uint8_t* data = app->worker.key.get_data();
 
     for(uint8_t i = 0; i < app->worker.key.get_type_data_count(); i++) {
         string_cat_printf(data_string, "%02X", data[i]);
@@ -12,8 +12,12 @@ void LfRfidAppSceneWrite::on_enter(LfRfidApp* app, bool need_restore) {
 
     auto popup = app->view_controller.get<PopupVM>();
 
-    popup->set_header("Writing", 90, 34, AlignCenter, AlignTop);
-    popup->set_text(string_get_cstr(data_string), 90, 48, AlignCenter, AlignTop);
+    popup->set_header("LF writing", 89, 30, AlignCenter, AlignTop);
+    if(strlen(app->worker.key.get_name())) {
+        popup->set_text(app->worker.key.get_name(), 89, 43, AlignCenter, AlignTop);
+    } else {
+        popup->set_text(string_get_cstr(data_string), 89, 43, AlignCenter, AlignTop);
+    }
     popup->set_icon(0, 4, I_RFIDDolphinSend_98x60);
 
     app->view_controller.switch_to<PopupVM>();

+ 68 - 0
lib/app-scened-template/file-worker-cpp.cpp

@@ -0,0 +1,68 @@
+#include "file-worker-cpp.h"
+#include <hex.h>
+
+FileWorkerCpp::FileWorkerCpp(bool _silent) {
+    file_worker = file_worker_alloc(_silent);
+}
+
+FileWorkerCpp::~FileWorkerCpp() {
+    file_worker_free(file_worker);
+}
+
+bool FileWorkerCpp::open(const char* filename, FS_AccessMode access_mode, FS_OpenMode open_mode) {
+    return file_worker_open(file_worker, filename, access_mode, open_mode);
+}
+
+bool FileWorkerCpp::close() {
+    return file_worker_close(file_worker);
+}
+
+bool FileWorkerCpp::mkdir(const char* dirname) {
+    return file_worker_mkdir(file_worker, dirname);
+}
+
+bool FileWorkerCpp::remove(const char* filename) {
+    return file_worker_remove(file_worker, filename);
+}
+
+bool FileWorkerCpp::read(void* buffer, uint16_t bytes_to_read) {
+    return file_worker_read(file_worker, buffer, bytes_to_read);
+}
+
+bool FileWorkerCpp::read_until(string_t str_result, char separator) {
+    return file_worker_read_until(file_worker, str_result, separator);
+}
+
+bool FileWorkerCpp::read_hex(uint8_t* buffer, uint16_t bytes_to_read) {
+    return file_worker_read_hex(file_worker, buffer, bytes_to_read);
+}
+
+bool FileWorkerCpp::tell(uint64_t* position) {
+    return file_worker_tell(file_worker, position);
+}
+
+bool FileWorkerCpp::seek(uint64_t position, bool from_start) {
+    return file_worker_seek(file_worker, position, from_start);
+}
+
+bool FileWorkerCpp::write(const void* buffer, uint16_t bytes_to_write) {
+    return file_worker_write(file_worker, buffer, bytes_to_write);
+}
+
+bool FileWorkerCpp::write_hex(const uint8_t* buffer, uint16_t bytes_to_write) {
+    return file_worker_write_hex(file_worker, buffer, bytes_to_write);
+}
+
+void FileWorkerCpp::show_error(const char* error_text) {
+    file_worker_show_error(file_worker, error_text);
+}
+
+bool FileWorkerCpp::file_select(
+    const char* path,
+    const char* extension,
+    char* result,
+    uint8_t result_size,
+    char* selected_filename) {
+    return file_worker_file_select(
+        file_worker, path, extension, result, result_size, selected_filename);
+}

+ 135 - 0
lib/app-scened-template/file-worker-cpp.h

@@ -0,0 +1,135 @@
+#pragma once
+#include "file-worker.h"
+
+/**
+ * @brief File operations helper class.
+ * Automatically opens API records, shows error message on error.
+ */
+class FileWorkerCpp {
+public:
+    FileWorkerCpp(bool silent = false);
+    ~FileWorkerCpp();
+
+    /**
+     * @brief Open file
+     * 
+     * @param filename 
+     * @param access_mode 
+     * @param open_mode 
+     * @return true on success
+     */
+    bool open(const char* filename, FS_AccessMode access_mode, FS_OpenMode open_mode);
+
+    /**
+     * @brief Close file
+     * 
+     * @return true on success
+     */
+    bool close();
+
+    /**
+     * @brief Creates a directory. Doesn't show error if directory exist. 
+     * 
+     * @param dirname 
+     * @return true on success
+     */
+    bool mkdir(const char* dirname);
+
+    /**
+     * @brief Removes the file. Doesn't show error if file doesn't exist.
+     * 
+     * @param filename 
+     * @return true on success  
+     */
+    bool remove(const char* filename);
+
+    /**
+     * @brief Reads data from a file.
+     * 
+     * @param buffer 
+     * @param bytes_to_read 
+     * @return true on success  
+     */
+    bool read(void* buffer, uint16_t bytes_to_read = 1);
+
+    /**
+     * @brief Reads data from a file until separator or EOF is found. 
+     * Moves seek pointer to the next symbol after the separator. The separator is not included in the result.
+     * 
+     * @param result 
+     * @param separator 
+     * @return true on success  
+     */
+    bool read_until(string_t result, char separator = '\n');
+
+    /**
+     * @brief Reads data in hexadecimal space-delimited format. For example "AF FF" in a file - [175, 255] in a read buffer.
+     * 
+     * @param buffer 
+     * @param bytes_to_read 
+     * @return true on success  
+     */
+    bool read_hex(uint8_t* buffer, uint16_t bytes_to_read = 1);
+
+    /**
+     * @brief Read seek pointer value
+     * 
+     * @param position 
+     * @return true on success  
+     */
+    bool tell(uint64_t* position);
+
+    /**
+     * @brief Set seek pointer value
+     * 
+     * @param position 
+     * @param from_start 
+     * @return true on success  
+     */
+    bool seek(uint64_t position, bool from_start);
+
+    /**
+     * @brief Write data to file.
+     * 
+     * @param buffer 
+     * @param bytes_to_write 
+     * @return true on success  
+     */
+    bool write(const void* buffer, uint16_t bytes_to_write = 1);
+
+    /**
+     * @brief Write data to file in hexadecimal space-delimited format. For example [175, 255] in a write buffer - "AF FF" in a file.
+     * 
+     * @param buffer 
+     * @param bytes_to_write 
+     * @return true on success  
+     */
+    bool write_hex(const uint8_t* buffer, uint16_t bytes_to_write = 1);
+
+    /**
+     * @brief Show system file error message
+     * 
+     * @param error_text 
+     */
+    void show_error(const char* error_text);
+
+    /**
+     * @brief Show system file select widget
+     * 
+     * @param path 
+     * @param extension 
+     * @param result 
+     * @param result_size 
+     * @param selected_filename 
+     * @return true if file was selected
+     */
+    bool file_select(
+        const char* path,
+        const char* extension,
+        char* result,
+        uint8_t result_size,
+        char* selected_filename);
+
+private:
+    FileWorker* file_worker;
+};

+ 280 - 0
lib/app-scened-template/file-worker.c

@@ -0,0 +1,280 @@
+#include "file-worker.h"
+#include <hex.h>
+#include <sd-card-api.h>
+#include <furi.h>
+
+struct FileWorker {
+    FS_Api* fs_api;
+    SdCard_Api* sd_ex_api;
+    bool silent;
+    File file;
+};
+
+bool file_worker_check_common_errors(FileWorker* file_worker);
+void file_worker_show_error_internal(FileWorker* file_worker, const char* error_text);
+bool file_worker_read_internal(FileWorker* file_worker, void* buffer, uint16_t bytes_to_read);
+bool file_worker_write_internal(
+    FileWorker* file_worker,
+    const void* buffer,
+    uint16_t bytes_to_write);
+bool file_worker_tell_internal(FileWorker* file_worker, uint64_t* position);
+bool file_worker_seek_internal(FileWorker* file_worker, uint64_t position, bool from_start);
+
+FileWorker* file_worker_alloc(bool _silent) {
+    FileWorker* file_worker = malloc(sizeof(FileWorker));
+    file_worker->silent = _silent;
+    file_worker->fs_api = furi_record_open("sdcard");
+    file_worker->sd_ex_api = furi_record_open("sdcard-ex");
+
+    return file_worker;
+}
+
+void file_worker_free(FileWorker* file_worker) {
+    furi_record_close("sdcard");
+    furi_record_close("sdcard-ex");
+    free(file_worker);
+}
+
+bool file_worker_open(
+    FileWorker* file_worker,
+    const char* filename,
+    FS_AccessMode access_mode,
+    FS_OpenMode open_mode) {
+    bool result =
+        file_worker->fs_api->file.open(&file_worker->file, filename, access_mode, open_mode);
+
+    if(!result) {
+        file_worker_show_error_internal(file_worker, "Cannot open\nfile");
+        return false;
+    }
+
+    return file_worker_check_common_errors(file_worker);
+}
+
+bool file_worker_close(FileWorker* file_worker) {
+    file_worker->fs_api->file.close(&file_worker->file);
+
+    return file_worker_check_common_errors(file_worker);
+}
+
+bool file_worker_mkdir(FileWorker* file_worker, const char* dirname) {
+    FS_Error fs_result = file_worker->fs_api->common.mkdir(dirname);
+
+    if(fs_result != FSE_OK && fs_result != FSE_EXIST) {
+        file_worker_show_error_internal(file_worker, "Cannot create\nfolder");
+        return false;
+    };
+
+    return file_worker_check_common_errors(file_worker);
+}
+
+bool file_worker_remove(FileWorker* file_worker, const char* filename) {
+    FS_Error fs_result = file_worker->fs_api->common.remove(filename);
+    if(fs_result != FSE_OK && fs_result != FSE_NOT_EXIST) {
+        file_worker_show_error_internal(file_worker, "Cannot remove\nold file");
+        return false;
+    };
+
+    return file_worker_check_common_errors(file_worker);
+}
+
+bool file_worker_read(FileWorker* file_worker, void* buffer, uint16_t bytes_to_read) {
+    if(!file_worker_read_internal(file_worker, buffer, bytes_to_read)) {
+        return false;
+    }
+
+    return file_worker_check_common_errors(file_worker);
+}
+
+bool file_worker_read_until(FileWorker* file_worker, string_t str_result, char separator) {
+    string_clean(str_result);
+    const uint8_t buffer_size = 32;
+    uint8_t buffer[buffer_size];
+
+    do {
+        uint16_t read_count =
+            file_worker->fs_api->file.read(&file_worker->file, buffer, buffer_size);
+        if(file_worker->file.error_id != FSE_OK) {
+            file_worker_show_error_internal(file_worker, "Cannot read\nfile");
+            return false;
+        }
+
+        bool result = false;
+        for(uint16_t i = 0; i < read_count; i++) {
+            if(buffer[i] == separator) {
+                uint64_t position;
+                if(!file_worker_tell_internal(file_worker, &position)) {
+                    return false;
+                }
+
+                position = position - read_count + i + 1;
+
+                if(!file_worker_seek_internal(file_worker, position, true)) {
+                    return false;
+                }
+
+                result = true;
+                break;
+            } else {
+                string_push_back(str_result, buffer[i]);
+            }
+        }
+
+        if(result || read_count == 0) {
+            break;
+        }
+    } while(true);
+
+    return file_worker_check_common_errors(file_worker);
+}
+
+bool file_worker_read_hex(FileWorker* file_worker, uint8_t* buffer, uint16_t bytes_to_read) {
+    uint8_t hi_nibble_value, low_nibble_value;
+    uint8_t text[2];
+
+    for(uint8_t i = 0; i < bytes_to_read; i++) {
+        if(i != 0) {
+            // space
+            if(!file_worker_read_internal(file_worker, text, 1)) {
+                return false;
+            }
+        }
+
+        // actual data
+        if(!file_worker_read_internal(file_worker, text, 2)) {
+            return false;
+        }
+
+        // convert hex value to byte
+        if(hex_char_to_hex_nibble(text[0], &hi_nibble_value) &&
+           hex_char_to_hex_nibble(text[1], &low_nibble_value)) {
+            buffer[i] = (hi_nibble_value << 4) | low_nibble_value;
+        } else {
+            file_worker_show_error_internal(file_worker, "Cannot parse\nfile");
+            return false;
+        }
+    }
+
+    return file_worker_check_common_errors(file_worker);
+}
+
+bool file_worker_tell(FileWorker* file_worker, uint64_t* position) {
+    if(!file_worker_tell_internal(file_worker, position)) {
+        return false;
+    }
+
+    return file_worker_check_common_errors(file_worker);
+}
+
+bool file_worker_seek(FileWorker* file_worker, uint64_t position, bool from_start) {
+    if(!file_worker_seek_internal(file_worker, position, from_start)) {
+        return false;
+    }
+
+    return file_worker_check_common_errors(file_worker);
+}
+
+bool file_worker_write(FileWorker* file_worker, const void* buffer, uint16_t bytes_to_write) {
+    if(!file_worker_write_internal(file_worker, buffer, bytes_to_write)) {
+        return false;
+    }
+
+    return file_worker_check_common_errors(file_worker);
+}
+
+bool file_worker_write_hex(FileWorker* file_worker, const uint8_t* buffer, uint16_t bytes_to_write) {
+    const uint8_t byte_text_size = 3;
+    char byte_text[byte_text_size];
+
+    for(uint8_t i = 0; i < bytes_to_write; i++) {
+        sniprintf(byte_text, byte_text_size, "%02X", buffer[i]);
+
+        if(i != 0) {
+            // space
+            const char* space = " ";
+            if(!file_worker_write_internal(file_worker, space, 1)) {
+                return false;
+            }
+        }
+
+        if(!file_worker_write_internal(file_worker, byte_text, 2)) {
+            return false;
+        }
+    }
+
+    return file_worker_check_common_errors(file_worker);
+}
+
+void file_worker_show_error(FileWorker* file_worker, const char* error_text) {
+    file_worker->sd_ex_api->show_error(file_worker->sd_ex_api->context, error_text);
+}
+
+bool file_worker_file_select(
+    FileWorker* file_worker,
+    const char* path,
+    const char* extension,
+    char* result,
+    uint8_t result_size,
+    char* selected_filename) {
+    return file_worker->sd_ex_api->file_select(
+        file_worker->sd_ex_api->context, path, extension, result, result_size, selected_filename);
+}
+
+bool file_worker_check_common_errors(FileWorker* file_worker) {
+    file_worker->sd_ex_api->check_error(file_worker->sd_ex_api->context);
+    return true;
+}
+
+void file_worker_show_error_internal(FileWorker* file_worker, const char* error_text) {
+    if(!file_worker->silent) {
+        file_worker_show_error(file_worker, error_text);
+    }
+}
+
+bool file_worker_read_internal(FileWorker* file_worker, void* buffer, uint16_t bytes_to_read) {
+    uint16_t read_count =
+        file_worker->fs_api->file.read(&file_worker->file, buffer, bytes_to_read);
+
+    if(file_worker->file.error_id != FSE_OK || read_count != bytes_to_read) {
+        file_worker_show_error_internal(file_worker, "Cannot read\nfile");
+        return false;
+    }
+
+    return true;
+}
+
+bool file_worker_write_internal(
+    FileWorker* file_worker,
+    const void* buffer,
+    uint16_t bytes_to_write) {
+    uint16_t write_count =
+        file_worker->fs_api->file.write(&file_worker->file, buffer, bytes_to_write);
+
+    if(file_worker->file.error_id != FSE_OK || write_count != bytes_to_write) {
+        file_worker_show_error_internal(file_worker, "Cannot write\nto file");
+        return false;
+    }
+
+    return true;
+}
+
+bool file_worker_tell_internal(FileWorker* file_worker, uint64_t* position) {
+    *position = file_worker->fs_api->file.tell(&file_worker->file);
+
+    if(file_worker->file.error_id != FSE_OK) {
+        file_worker_show_error_internal(file_worker, "Cannot tell\nfile offset");
+        return false;
+    }
+
+    return true;
+}
+
+bool file_worker_seek_internal(FileWorker* file_worker, uint64_t position, bool from_start) {
+    file_worker->fs_api->file.seek(&file_worker->file, position, from_start);
+    if(file_worker->file.error_id != FSE_OK) {
+        file_worker_show_error_internal(file_worker, "Cannot seek\nfile");
+        return false;
+    }
+
+    return true;
+}

+ 0 - 237
lib/app-scened-template/file-worker.cpp

@@ -1,237 +0,0 @@
-#include "file-worker.h"
-
-FileWorker::FileWorker(bool _silent)
-    : fs_api{"sdcard"}
-    , sd_ex_api{"sdcard-ex"} {
-    silent = _silent;
-}
-
-FileWorker::~FileWorker() {
-}
-
-bool FileWorker::open(const char* filename, FS_AccessMode access_mode, FS_OpenMode open_mode) {
-    bool result = fs_api.get()->file.open(&file, filename, access_mode, open_mode);
-
-    if(!result) {
-        show_error_internal("Cannot open\nfile");
-        close();
-        return false;
-    }
-
-    return check_common_errors();
-}
-
-bool FileWorker::close() {
-    fs_api.get()->file.close(&file);
-
-    return check_common_errors();
-}
-
-bool FileWorker::mkdir(const char* dirname) {
-    FS_Error fs_result = fs_api.get()->common.mkdir(dirname);
-
-    if(fs_result != FSE_OK && fs_result != FSE_EXIST) {
-        show_error_internal("Cannot create\nfolder");
-        return false;
-    };
-
-    return check_common_errors();
-}
-
-bool FileWorker::remove(const char* filename) {
-    FS_Error fs_result = fs_api.get()->common.remove(filename);
-    if(fs_result != FSE_OK && fs_result != FSE_NOT_EXIST) {
-        show_error_internal("Cannot remove\nold file");
-        return false;
-    };
-
-    return check_common_errors();
-}
-
-bool FileWorker::read(void* buffer, uint16_t bytes_to_read) {
-    if(!read_internal(buffer, bytes_to_read)) {
-        return false;
-    }
-
-    return check_common_errors();
-}
-
-bool FileWorker::read_until(string_t str_result, char separator) {
-    string_clean(str_result);
-    const uint8_t buffer_size = 32;
-    uint8_t buffer[buffer_size];
-
-    do {
-        uint16_t read_count = fs_api.get()->file.read(&file, buffer, buffer_size);
-        if(file.error_id != FSE_OK) {
-            show_error_internal("Cannot read\nfile");
-            return false;
-        }
-
-        bool result = false;
-        for(uint16_t i = 0; i < read_count; i++) {
-            if(buffer[i] == separator) {
-                uint64_t position;
-                if(!tell_internal(&position)) {
-                    return false;
-                }
-
-                position = position - read_count + i + 1;
-
-                if(!seek_internal(position, true)) {
-                    return false;
-                }
-
-                result = true;
-                break;
-            } else {
-                string_push_back(str_result, buffer[i]);
-            }
-        }
-
-        if(result || read_count == 0) {
-            break;
-        }
-    } while(true);
-
-    return check_common_errors();
-}
-
-bool FileWorker::read_hex(uint8_t* buffer, uint16_t bytes_to_read) {
-    uint8_t text[2];
-
-    for(uint8_t i = 0; i < bytes_to_read; i++) {
-        if(i != 0) {
-            // space
-            if(!read_internal(text, 1)) {
-                return false;
-            }
-        }
-
-        // actual data
-        if(!read_internal(text, 2)) {
-            return false;
-        }
-
-        // convert hex value to byte
-        buffer[i] = strtol(reinterpret_cast<char*>(text), NULL, 16);
-    }
-
-    return check_common_errors();
-}
-
-bool FileWorker::tell(uint64_t* position) {
-    if(!tell_internal(position)) {
-        return false;
-    }
-
-    return check_common_errors();
-}
-
-bool FileWorker::seek(uint64_t position, bool from_start) {
-    if(!seek_internal(position, from_start)) {
-        return false;
-    }
-
-    return check_common_errors();
-}
-
-bool FileWorker::write(const void* buffer, uint16_t bytes_to_write) {
-    if(!write_internal(buffer, bytes_to_write)) {
-        return false;
-    }
-
-    return check_common_errors();
-}
-
-bool FileWorker::write_hex(const uint8_t* buffer, uint16_t bytes_to_write) {
-    const uint8_t byte_text_size = 3;
-    char byte_text[byte_text_size];
-
-    for(uint8_t i = 0; i < bytes_to_write; i++) {
-        sniprintf(byte_text, byte_text_size, "%02X", buffer[i]);
-
-        if(i != 0) {
-            // space
-            const char* space = " ";
-            if(!write_internal(space, 1)) {
-                return false;
-            }
-        }
-
-        if(!write_internal(byte_text, 2)) {
-            return false;
-        }
-    }
-
-    return check_common_errors();
-}
-
-void FileWorker::show_error(const char* error_text) {
-    sd_ex_api.get()->show_error(sd_ex_api.get()->context, error_text);
-}
-
-bool FileWorker::file_select(
-    const char* path,
-    const char* extension,
-    char* result,
-    uint8_t result_size,
-    char* selected_filename) {
-    return sd_ex_api.get()->file_select(
-        sd_ex_api.get()->context, path, extension, result, result_size, selected_filename);
-
-}
-
-bool FileWorker::check_common_errors() {
-    sd_ex_api.get()->check_error(sd_ex_api.get()->context);
-    return true;
-}
-
-void FileWorker::show_error_internal(const char* error_text) {
-    if(!silent) {
-        show_error(error_text);
-    }
-}
-
-bool FileWorker::read_internal(void* buffer, uint16_t bytes_to_read) {
-    uint16_t read_count = fs_api.get()->file.read(&file, buffer, bytes_to_read);
-
-    if(file.error_id != FSE_OK || read_count != bytes_to_read) {
-        show_error_internal("Cannot read\nfile");
-        return false;
-    }
-
-    return true;
-}
-
-bool FileWorker::write_internal(const void* buffer, uint16_t bytes_to_write) {
-    uint16_t write_count = fs_api.get()->file.write(&file, buffer, bytes_to_write);
-
-    if(file.error_id != FSE_OK || write_count != bytes_to_write) {
-        show_error_internal("Cannot write\nto file");
-        return false;
-    }
-
-    return true;
-}
-
-bool FileWorker::tell_internal(uint64_t* position) {
-    *position = fs_api.get()->file.tell(&file);
-
-    if(file.error_id != FSE_OK) {
-        show_error_internal("Cannot tell\nfile offset");
-        return false;
-    }
-
-    return true;
-}
-
-bool FileWorker::seek_internal(uint64_t position, bool from_start) {
-    fs_api.get()->file.seek(&file, position, from_start);
-    if(file.error_id != FSE_OK) {
-        show_error_internal("Cannot seek\nfile");
-        return false;
-    }
-
-    return true;
-}

+ 163 - 144
lib/app-scened-template/file-worker.h

@@ -1,151 +1,170 @@
 #pragma once
-#include "record-controller.hpp"
-#include <sd-card-api.h>
-#include <filesystem-api.h>
 #include <m-string.h>
+#include <filesystem-api.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
 
 /**
  * @brief File operations helper class.
  * Automatically opens API records, shows error message on error.
  */
-class FileWorker {
-public:
-    FileWorker(bool silent = false);
-    ~FileWorker();
-
-    RecordController<FS_Api> fs_api;
-    RecordController<SdCard_Api> sd_ex_api;
-
-    /**
-     * @brief Open file
-     * 
-     * @param filename 
-     * @param access_mode 
-     * @param open_mode 
-     * @return true on success
-     */
-    bool open(const char* filename, FS_AccessMode access_mode, FS_OpenMode open_mode);
-
-    /**
-     * @brief Close file
-     * 
-     * @return true on success
-     */
-    bool close();
-
-    /**
-     * @brief Creates a directory. Doesn't show error if directory exist. 
-     * 
-     * @param dirname 
-     * @return true on success
-     */
-    bool mkdir(const char* dirname);
-
-    /**
-     * @brief Removes the file. Doesn't show error if file doesn't exist.
-     * 
-     * @param filename 
-     * @return true on success  
-     */
-    bool remove(const char* filename);
-
-    /**
-     * @brief Reads data from a file.
-     * 
-     * @param buffer 
-     * @param bytes_to_read 
-     * @return true on success  
-     */
-    bool read(void* buffer, uint16_t bytes_to_read = 1);
-
-    /**
-     * @brief Reads data from a file until separator or EOF is found. 
-     * Moves seek pointer to the next symbol after the separator. The separator is not included in the result.
-     * 
-     * @param result 
-     * @param separator 
-     * @return true on success  
-     */
-    bool read_until(string_t result, char separator = '\n');
-
-    /**
-     * @brief Reads data in hexadecimal space-delimited format. For example "AF FF" in a file - [175, 255] in a read buffer.
-     * 
-     * @param buffer 
-     * @param bytes_to_read 
-     * @return true on success  
-     */
-    bool read_hex(uint8_t* buffer, uint16_t bytes_to_read = 1);
-
-    /**
-     * @brief Read seek pointer value
-     * 
-     * @param position 
-     * @return true on success  
-     */
-    bool tell(uint64_t* position);
-
-    /**
-     * @brief Set seek pointer value
-     * 
-     * @param position 
-     * @param from_start 
-     * @return true on success  
-     */
-    bool seek(uint64_t position, bool from_start);
-
-    /**
-     * @brief Write data to file.
-     * 
-     * @param buffer 
-     * @param bytes_to_write 
-     * @return true on success  
-     */
-    bool write(const void* buffer, uint16_t bytes_to_write = 1);
-
-    /**
-     * @brief Write data to file in hexadecimal space-delimited format. For example [175, 255] in a write buffer - "AF FF" in a file.
-     * 
-     * @param buffer 
-     * @param bytes_to_write 
-     * @return true on success  
-     */
-    bool write_hex(const uint8_t* buffer, uint16_t bytes_to_write = 1);
-
-    /**
-     * @brief Show system file error message
-     * 
-     * @param error_text 
-     */
-    void show_error(const char* error_text);
-
-    /**
-     * @brief Show system file select widget
-     * 
-     * @param path 
-     * @param extension 
-     * @param result 
-     * @param result_size 
-     * @param selected_filename 
-     * @return true if file was selected
-     */
-    bool file_select(
-        const char* path,
-        const char* extension,
-        char* result,
-        uint8_t result_size,
-        char* selected_filename);
-
-private:
-    File file;
-    bool silent;
-
-    bool check_common_errors();
-
-    void show_error_internal(const char* error_text);
-
-    bool read_internal(void* buffer, uint16_t bytes_to_read = 1);
-    bool write_internal(const void* buffer, uint16_t bytes_to_write = 1);
-    bool tell_internal(uint64_t* position);
-    bool seek_internal(uint64_t position, bool from_start);
-};
+typedef struct FileWorker FileWorker;
+
+/**
+ * @brief Allocate FileWorker
+ * 
+ * @param silent do not show errors except from file_worker_show_error fn
+ * @return FileWorker* 
+ */
+FileWorker* file_worker_alloc(bool silent);
+
+/**
+ * @brief free FileWorker
+ * 
+ * @param file_worker 
+ */
+void file_worker_free(FileWorker* file_worker);
+
+/**
+ * @brief Open file
+ * 
+ * @param file_worker FileWorker instance 
+ * @param filename 
+ * @param access_mode 
+ * @param open_mode 
+ * @return true on success
+ */
+bool file_worker_open(
+    FileWorker* file_worker,
+    const char* filename,
+    FS_AccessMode access_mode,
+    FS_OpenMode open_mode);
+
+/**
+ * @brief Close file
+ * 
+ * @param file_worker FileWorker instance 
+ * @return true on success
+ */
+bool file_worker_close(FileWorker* file_worker);
+
+/**
+ * @brief Creates a directory. Doesn't show error if directory exist. 
+ * 
+ * @param file_worker FileWorker instance 
+ * @param dirname 
+ * @return true on success
+ */
+bool file_worker_mkdir(FileWorker* file_worker, const char* dirname);
+
+/**
+ * @brief Removes the file. Doesn't show error if file doesn't exist.
+ * 
+ * @param file_worker FileWorker instance 
+ * @param filename 
+ * @return true on success  
+ */
+bool file_worker_remove(FileWorker* file_worker, const char* filename);
+
+/**
+ * @brief Reads data from a file.
+ * 
+ * @param file_worker FileWorker instance 
+ * @param buffer 
+ * @param bytes_to_read 
+ * @return true on success  
+ */
+bool file_worker_read(FileWorker* file_worker, void* buffer, uint16_t bytes_to_read);
+
+/**
+ * @brief Reads data from a file until separator or EOF is found. 
+ * Moves seek pointer to the next symbol after the separator. The separator is not included in the result.
+ * 
+ * @param file_worker FileWorker instance 
+ * @param result 
+ * @param separator 
+ * @return true on success  
+ */
+bool file_worker_read_until(FileWorker* file_worker, string_t result, char separator);
+
+/**
+ * @brief Reads data in hexadecimal space-delimited format. For example "AF FF" in a file - [175, 255] in a read buffer.
+ * 
+ * @param file_worker FileWorker instance 
+ * @param buffer 
+ * @param bytes_to_read 
+ * @return true on success  
+ */
+bool file_worker_read_hex(FileWorker* file_worker, uint8_t* buffer, uint16_t bytes_to_read);
+
+/**
+ * @brief Read seek pointer value
+ * 
+ * @param file_worker FileWorker instance 
+ * @param position 
+ * @return true on success  
+ */
+bool file_worker_tell(FileWorker* file_worker, uint64_t* position);
+
+/**
+ * @brief Set seek pointer value
+ * 
+ * @param file_worker FileWorker instance 
+ * @param position 
+ * @param from_start 
+ * @return true on success  
+ */
+bool file_worker_seek(FileWorker* file_worker, uint64_t position, bool from_start);
+
+/**
+ * @brief Write data to file.
+ * 
+ * @param file_worker FileWorker instance 
+ * @param buffer 
+ * @param bytes_to_write 
+ * @return true on success  
+ */
+bool file_worker_write(FileWorker* file_worker, const void* buffer, uint16_t bytes_to_write);
+
+/**
+ * @brief Write data to file in hexadecimal space-delimited format. For example [175, 255] in a write buffer - "AF FF" in a file.
+ * 
+ * @param file_worker FileWorker instance 
+ * @param buffer 
+ * @param bytes_to_write 
+ * @return true on success  
+ */
+bool file_worker_write_hex(FileWorker* file_worker, const uint8_t* buffer, uint16_t bytes_to_write);
+
+/**
+ * @brief Show system file error message
+ * 
+ * @param file_worker FileWorker instance 
+ * @param error_text 
+ */
+void file_worker_show_error(FileWorker* file_worker, const char* error_text);
+
+/**
+ * @brief Show system file select widget
+ * 
+ * @param file_worker FileWorker instance 
+ * @param path 
+ * @param extension 
+ * @param result 
+ * @param result_size 
+ * @param selected_filename 
+ * @return true if file was selected
+ */
+bool file_worker_file_select(
+    FileWorker* file_worker,
+    const char* path,
+    const char* extension,
+    char* result,
+    uint8_t result_size,
+    char* selected_filename);
+
+#ifdef __cplusplus
+}
+#endif

+ 72 - 7
lib/app-scened-template/scene-controller.hpp

@@ -2,7 +2,7 @@
 #include <forward_list>
 #include <initializer_list>
 
-#define GENERIC_SCENE_ENUM_VALUES Uninitalized, Exit, Start
+#define GENERIC_SCENE_ENUM_VALUES Exit, Start
 #define GENERIC_EVENT_ENUM_VALUES Tick, Back
 
 /**
@@ -54,22 +54,85 @@ public:
      * 
      * @param scene_index_list list of scene indexes to which you want to switch
      */
-    void search_and_switch_to_previous_scene(
+    bool search_and_switch_to_previous_scene(
         const std::initializer_list<typename TApp::SceneType>& scene_index_list) {
-        auto previous_scene_index = TApp::SceneType::Start;
+        auto previous_scene_index = TApp::SceneType::Exit;
         bool scene_found = false;
+        bool result = false;
 
         while(!scene_found) {
             previous_scene_index = get_previous_scene_index();
             for(const auto& element : scene_index_list) {
                 if(previous_scene_index == element) {
+                    scene_found = true;
+                    result = true;
+                    break;
+                }
+
+                if(previous_scene_index == TApp::SceneType::Exit) {
                     scene_found = true;
                     break;
                 }
             }
         }
 
-        switch_to_scene(previous_scene_index, true);
+        if(result) {
+            switch_to_scene(previous_scene_index, true);
+        }
+
+        return result;
+    }
+
+    bool search_and_switch_to_another_scene(
+        const std::initializer_list<typename TApp::SceneType>& scene_index_list,
+        typename TApp::SceneType scene_index) {
+        auto previous_scene_index = TApp::SceneType::Exit;
+        bool scene_found = false;
+        bool result = false;
+
+        while(!scene_found) {
+            previous_scene_index = get_previous_scene_index();
+            for(const auto& element : scene_index_list) {
+                if(previous_scene_index == element) {
+                    scene_found = true;
+                    result = true;
+                    break;
+                }
+
+                if(previous_scene_index == TApp::SceneType::Exit) {
+                    scene_found = true;
+                    break;
+                }
+            }
+        }
+
+        if(result) {
+            switch_to_scene(scene_index, true);
+        }
+
+        return result;
+    }
+
+    bool
+    has_previous_scene(const std::initializer_list<typename TApp::SceneType>& scene_index_list) {
+        bool result = false;
+
+        for(auto const& previous_element : previous_scenes_list) {
+            for(const auto& element : scene_index_list) {
+                if(previous_element == element) {
+                    result = true;
+                    break;
+                }
+
+                if(previous_element == TApp::SceneType::Exit) {
+                    break;
+                }
+            }
+
+            if(result) break;
+        }
+
+        return result;
     }
 
     /**
@@ -77,12 +140,14 @@ public:
      * 
      * @param tick_length_ms tick event length in milliseconds
      */
-    void process(uint32_t tick_length_ms = 100) {
+    void process(
+        uint32_t tick_length_ms = 100,
+        typename TApp::SceneType start_scene_index = TApp::SceneType::Start) {
         typename TApp::Event event;
         bool consumed;
         bool exit = false;
 
-        current_scene_index = TApp::SceneType::Start;
+        current_scene_index = start_scene_index;
         scenes[current_scene_index]->on_enter(app, false);
 
         while(!exit) {
@@ -124,7 +189,7 @@ public:
      */
     SceneController(TApp* app_pointer) {
         app = app_pointer;
-        current_scene_index = TApp::SceneType::Uninitalized;
+        current_scene_index = TApp::SceneType::Exit;
     }
 
     /**

+ 3 - 17
lib/args/args.c

@@ -1,4 +1,5 @@
 #include "args.h"
+#include "hex.h"
 
 size_t args_get_first_word_length(string_t args) {
     size_t ws = string_search_char(args, ' ');
@@ -27,28 +28,13 @@ bool args_read_string_and_trim(string_t args, string_t word) {
     return true;
 }
 
-bool args_char_to_hex_nibble(char c, uint8_t* nibble) {
-    if((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) {
-        if((c >= '0' && c <= '9')) {
-            *nibble = c - '0';
-        } else if((c >= 'A' && c <= 'F')) {
-            *nibble = c - 'A' + 10;
-        } else {
-            *nibble = c - 'a' + 10;
-        }
-        return true;
-    } else {
-        return false;
-    }
-}
-
 bool args_char_to_hex(char hi_nibble, char low_nibble, uint8_t* byte) {
     uint8_t hi_nibble_value = 0;
     uint8_t low_nibble_value = 0;
     bool result = false;
 
-    if(args_char_to_hex_nibble(hi_nibble, &hi_nibble_value)) {
-        if(args_char_to_hex_nibble(low_nibble, &low_nibble_value)) {
+    if(hex_char_to_hex_nibble(hi_nibble, &hi_nibble_value)) {
+        if(hex_char_to_hex_nibble(low_nibble, &low_nibble_value)) {
             result = true;
             *byte = (hi_nibble_value << 4) | low_nibble_value;
         }

+ 0 - 9
lib/args/args.h

@@ -46,15 +46,6 @@ size_t args_get_first_word_length(string_t args);
  */
 size_t args_length(string_t args);
 
-/**
- * @brief Convert ASCII hex value to nibble 
- * 
- * @param c ASCII character
- * @param nibble nibble pointer, output
- * @return bool conversion status
- */
-bool args_char_to_hex_nibble(char c, uint8_t* nibble);
-
 /**
  * @brief Convert ASCII hex values to byte
  * 

+ 16 - 0
lib/args/hex.c

@@ -0,0 +1,16 @@
+#include "hex.h"
+
+bool hex_char_to_hex_nibble(char c, uint8_t* nibble) {
+    if((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) {
+        if((c >= '0' && c <= '9')) {
+            *nibble = c - '0';
+        } else if((c >= 'A' && c <= 'F')) {
+            *nibble = c - 'A' + 10;
+        } else {
+            *nibble = c - 'a' + 10;
+        }
+        return true;
+    } else {
+        return false;
+    }
+}

+ 20 - 0
lib/args/hex.h

@@ -0,0 +1,20 @@
+#pragma once
+#include "stdint.h"
+#include "stdbool.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Convert ASCII hex value to nibble 
+ * 
+ * @param c ASCII character
+ * @param nibble nibble pointer, output
+ * @return bool conversion status
+ */
+bool hex_char_to_hex_nibble(char c, uint8_t* nibble);
+
+#ifdef __cplusplus
+}
+#endif

+ 20 - 0
lib/args/path.c

@@ -0,0 +1,20 @@
+#include "path.h"
+
+void path_extract_filename_no_ext(const char* path, string_t filename) {
+    string_set(filename, path);
+
+    size_t start_position = string_search_rchar(filename, '/');
+    size_t end_position = string_search_rchar(filename, '.');
+
+    if(start_position == STRING_FAILURE) {
+        start_position = 0;
+    } else {
+        start_position += 1;
+    }
+
+    if(end_position == STRING_FAILURE) {
+        end_position = string_size(filename);
+    }
+
+    string_mid(filename, start_position, end_position - start_position);
+}

+ 18 - 0
lib/args/path.h

@@ -0,0 +1,18 @@
+#pragma once
+#include "m-string.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Extract filename without extension from path.
+ * 
+ * @param path path string
+ * @param filename output filename string. Must be initialized before.
+ */
+void path_extract_filename_no_ext(const char* path, string_t filename);
+
+#ifdef __cplusplus
+}
+#endif

+ 1 - 0
lib/lib.mk

@@ -105,5 +105,6 @@ C_SOURCES		+= $(wildcard $(LIB_DIR)/fl_subghz/*/*.c)
 
 #scened app template lib
 CFLAGS			+= -I$(LIB_DIR)/app-scened-template
+C_SOURCES		+= $(wildcard $(LIB_DIR)/app-scened-template/*.c)
 CPP_SOURCES		+= $(wildcard $(LIB_DIR)/app-scened-template/*.cpp)
 CPP_SOURCES		+= $(wildcard $(LIB_DIR)/app-scened-template/*/*.cpp)