diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1a0a91e6..d37622f1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,7 @@ env: jobs: main: - runs-on: [self-hosted,Office] + runs-on: [self-hosted,FlipperZero] steps: - name: 'Cleanup workspace' uses: AutoModality/action-clean@v1 @@ -166,7 +166,7 @@ jobs: compact: if: ${{ !startsWith(github.ref, 'refs/tags') }} - runs-on: [self-hosted,koteeq] + runs-on: [self-hosted,FlipperZero] steps: - name: 'Cleanup workspace' uses: AutoModality/action-clean@v1 diff --git a/.github/workflows/lint_c.yml b/.github/workflows/lint_c.yml index 52a5e9d7..97ef9fb9 100644 --- a/.github/workflows/lint_c.yml +++ b/.github/workflows/lint_c.yml @@ -14,7 +14,7 @@ env: jobs: lint_c_cpp: - runs-on: [self-hosted,Office] + runs-on: [self-hosted,FlipperZero] steps: - name: 'Cleanup workspace' uses: AutoModality/action-clean@v1 diff --git a/.github/workflows/reindex.yml b/.github/workflows/reindex.yml index 49c47eec..78f9cbea 100644 --- a/.github/workflows/reindex.yml +++ b/.github/workflows/reindex.yml @@ -7,7 +7,7 @@ on: jobs: reindex: name: 'Reindex updates' - runs-on: [self-hosted,Office] + runs-on: [self-hosted,FlipperZero] steps: - name: Trigger reindex uses: wei/curl@master diff --git a/ReadMe.md b/ReadMe.md index c6b73c8b..9e34148f 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -7,6 +7,13 @@ Welcome to [Flipper Zero](https://flipperzero.one/)'s Firmware repo! Our goal is to create nice and clean code with good documentation, to make it a pleasure for everyone to work with. +# Clone the Repository + +You should clone with +```shell +$ git clone --recursive https://github.com/flipperdevices/flipperzero-firmware.git +``` + # Update firmware [Get Latest Firmware from Update Server](https://update.flipperzero.one/) @@ -74,6 +81,8 @@ Check `dist/` for build outputs. Use **`flipper-z-{target}-full-{suffix}.dfu`** to flash your device. +If compilation fails, make sure all submodules are all initialized. Either clone with `--recursive` or use `git submodule update --init --recursive`. + # Build on Linux/macOS ## macOS Prerequisites diff --git a/applications/applications.c b/applications/applications.c index 4deae885..dccab589 100644 --- a/applications/applications.c +++ b/applications/applications.c @@ -44,6 +44,7 @@ extern int32_t vibro_test_app(void* p); extern int32_t bt_hid_app(void* p); extern int32_t battery_test_app(void* p); extern int32_t text_box_test_app(void* p); +extern int32_t file_browser_app(void* p); // Plugins extern int32_t music_player_app(void* p); @@ -419,14 +420,6 @@ const FlipperApplication FLIPPER_DEBUG_APPS[] = { .flags = FlipperApplicationFlagDefault}, #endif -#ifdef APP_SCENED - {.app = scened_app, - .name = "Templated Scene", - .stack_size = 1024, - .icon = NULL, - .flags = FlipperApplicationFlagDefault}, -#endif - #ifdef APP_LF_RFID {.app = lfrfid_debug_app, .name = "LF-RFID Debug", @@ -459,6 +452,14 @@ const FlipperApplication FLIPPER_DEBUG_APPS[] = { .flags = FlipperApplicationFlagDefault}, #endif +#ifdef APP_FILE_BROWSER_TEST + {.app = file_browser_app, + .name = "File Browser test", + .stack_size = 2048, + .icon = &A_BadUsb_14, + .flags = FlipperApplicationFlagDefault}, +#endif + #ifdef APP_BATTERY_TEST {.app = battery_test_app, .name = "Battery Test", diff --git a/applications/applications.mk b/applications/applications.mk index efe6aa42..e7a9ebfc 100644 --- a/applications/applications.mk +++ b/applications/applications.mk @@ -62,6 +62,7 @@ APP_USB_MOUSE = 1 APP_BAD_USB = 1 APP_U2F = 1 APP_UART_ECHO = 1 +APP_FILE_BROWSER_TEST = 1 endif @@ -207,6 +208,11 @@ CFLAGS += -DAPP_KEYPAD_TEST SRV_GUI = 1 endif +APP_FILE_BROWSER_TEST ?= 0 +ifeq ($(APP_FILE_BROWSER_TEST), 1) +CFLAGS += -DAPP_FILE_BROWSER_TEST +SRV_GUI = 1 +endif APP_ACCESSOR ?= 0 ifeq ($(APP_ACCESSOR), 1) diff --git a/applications/bad_usb/bad_usb_app.c b/applications/bad_usb/bad_usb_app.c index eb647e00..65ccc575 100644 --- a/applications/bad_usb/bad_usb_app.c +++ b/applications/bad_usb/bad_usb_app.c @@ -1,4 +1,5 @@ #include "bad_usb_app_i.h" +#include "m-string.h" #include #include #include @@ -22,33 +23,13 @@ static void bad_usb_app_tick_event_callback(void* context) { scene_manager_handle_tick_event(app->scene_manager); } -static bool bad_usb_check_assets() { - Storage* fs_api = furi_record_open("storage"); - - File* dir = storage_file_alloc(fs_api); - bool ret = false; - - if(storage_dir_open(dir, BAD_USB_APP_PATH_FOLDER)) { - ret = true; - } - - storage_dir_close(dir); - storage_file_free(dir); - - furi_record_close("storage"); - - return ret; -} - BadUsbApp* bad_usb_app_alloc(char* arg) { BadUsbApp* app = malloc(sizeof(BadUsbApp)); + string_init(app->file_path); + if(arg != NULL) { - string_t filename; - string_init(filename); - path_extract_filename_no_ext(arg, filename); - strncpy(app->file_name, string_get_cstr(filename), BAD_USB_FILE_NAME_LEN); - string_clear(filename); + string_set_str(app->file_path, arg); } app->gui = furi_record_open("gui"); @@ -83,13 +64,11 @@ BadUsbApp* bad_usb_app_alloc(char* arg) { app->error = BadUsbAppErrorCloseRpc; scene_manager_next_scene(app->scene_manager, BadUsbSceneError); } else { - if(*app->file_name != '\0') { + if(!string_empty_p(app->file_path)) { scene_manager_next_scene(app->scene_manager, BadUsbSceneWork); - } else if(bad_usb_check_assets()) { - scene_manager_next_scene(app->scene_manager, BadUsbSceneFileSelect); } else { - app->error = BadUsbAppErrorNoFiles; - scene_manager_next_scene(app->scene_manager, BadUsbSceneError); + string_set_str(app->file_path, BAD_USB_APP_PATH_FOLDER); + scene_manager_next_scene(app->scene_manager, BadUsbSceneFileSelect); } } @@ -117,6 +96,8 @@ void bad_usb_app_free(BadUsbApp* app) { furi_record_close("notification"); furi_record_close("dialogs"); + string_clear(app->file_path); + free(app); } diff --git a/applications/bad_usb/bad_usb_app_i.h b/applications/bad_usb/bad_usb_app_i.h index 67f5816b..c82419e0 100644 --- a/applications/bad_usb/bad_usb_app_i.h +++ b/applications/bad_usb/bad_usb_app_i.h @@ -16,7 +16,6 @@ #define BAD_USB_APP_PATH_FOLDER "/any/badusb" #define BAD_USB_APP_EXTENSION ".txt" -#define BAD_USB_FILE_NAME_LEN 40 typedef enum { BadUsbAppErrorNoFiles, @@ -32,7 +31,7 @@ struct BadUsbApp { Widget* widget; BadUsbAppError error; - char file_name[BAD_USB_FILE_NAME_LEN + 1]; + string_t file_path; BadUsb* bad_usb_view; BadUsbScript* bad_usb_script; }; diff --git a/applications/bad_usb/scenes/bad_usb_scene_file_select.c b/applications/bad_usb/scenes/bad_usb_scene_file_select.c index 82f03bab..1e6ba895 100644 --- a/applications/bad_usb/scenes/bad_usb_scene_file_select.c +++ b/applications/bad_usb/scenes/bad_usb_scene_file_select.c @@ -5,14 +5,16 @@ static bool bad_usb_file_select(BadUsbApp* bad_usb) { furi_assert(bad_usb); - // Input events and views are managed by file_select - bool res = dialog_file_select_show( + // Input events and views are managed by file_browser + bool res = dialog_file_browser_show( bad_usb->dialogs, - BAD_USB_APP_PATH_FOLDER, + bad_usb->file_path, + bad_usb->file_path, BAD_USB_APP_EXTENSION, - bad_usb->file_name, - sizeof(bad_usb->file_name), - NULL); + true, + &I_badusb_10px, + true); + return res; } diff --git a/applications/bad_usb/scenes/bad_usb_scene_work.c b/applications/bad_usb/scenes/bad_usb_scene_work.c index a3a46803..516cbde3 100644 --- a/applications/bad_usb/scenes/bad_usb_scene_work.c +++ b/applications/bad_usb/scenes/bad_usb_scene_work.c @@ -2,6 +2,8 @@ #include "../bad_usb_app_i.h" #include "../views/bad_usb_view.h" #include "furi_hal.h" +#include "m-string.h" +#include "toolbox/path.h" void bad_usb_scene_work_ok_callback(InputType type, void* context) { furi_assert(context); @@ -28,10 +30,9 @@ void bad_usb_scene_work_on_enter(void* context) { string_t file_name; string_init(file_name); - bad_usb_set_file_name(app->bad_usb_view, app->file_name); - string_printf( - file_name, "%s/%s%s", BAD_USB_APP_PATH_FOLDER, app->file_name, BAD_USB_APP_EXTENSION); - app->bad_usb_script = bad_usb_script_open(file_name); + path_extract_filename(app->file_path, file_name, true); + bad_usb_set_file_name(app->bad_usb_view, string_get_cstr(file_name)); + app->bad_usb_script = bad_usb_script_open(app->file_path); string_clear(file_name); diff --git a/applications/bad_usb/views/bad_usb_view.c b/applications/bad_usb/views/bad_usb_view.c index 5b6fe6e8..430885df 100644 --- a/applications/bad_usb/views/bad_usb_view.c +++ b/applications/bad_usb/views/bad_usb_view.c @@ -2,6 +2,8 @@ #include "../bad_usb_script.h" #include +#define MAX_NAME_LEN 64 + struct BadUsb { View* view; BadUsbOkCallback callback; @@ -9,7 +11,7 @@ struct BadUsb { }; typedef struct { - char* file_name; + char file_name[MAX_NAME_LEN]; BadUsbState state; uint8_t anim_frame; } BadUsbModel; @@ -149,11 +151,11 @@ void bad_usb_set_ok_callback(BadUsb* bad_usb, BadUsbOkCallback callback, void* c }); } -void bad_usb_set_file_name(BadUsb* bad_usb, char* name) { +void bad_usb_set_file_name(BadUsb* bad_usb, const char* name) { furi_assert(name); with_view_model( bad_usb->view, (BadUsbModel * model) { - model->file_name = name; + strncpy(model->file_name, name, MAX_NAME_LEN); return true; }); } diff --git a/applications/bad_usb/views/bad_usb_view.h b/applications/bad_usb/views/bad_usb_view.h index f5a8a0fa..80a47e2c 100755 --- a/applications/bad_usb/views/bad_usb_view.h +++ b/applications/bad_usb/views/bad_usb_view.h @@ -14,6 +14,6 @@ View* bad_usb_get_view(BadUsb* bad_usb); void bad_usb_set_ok_callback(BadUsb* bad_usb, BadUsbOkCallback callback, void* context); -void bad_usb_set_file_name(BadUsb* bad_usb, char* name); +void bad_usb_set_file_name(BadUsb* bad_usb, const char* name); void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st); diff --git a/applications/bt/bt_service/bt.c b/applications/bt/bt_service/bt.c old mode 100755 new mode 100644 index 111ebcb2..38aadc49 --- a/applications/bt/bt_service/bt.c +++ b/applications/bt/bt_service/bt.c @@ -80,7 +80,7 @@ static bool bt_pin_code_verify_event_handler(Bt* bt, uint32_t pin) { string_init_printf(pin_str, "Verify code\n%06d", pin); dialog_message_set_text( bt->dialog_message, string_get_cstr(pin_str), 64, 4, AlignCenter, AlignTop); - dialog_message_set_buttons(bt->dialog_message, "Cancel", "Ok", NULL); + dialog_message_set_buttons(bt->dialog_message, "Cancel", "OK", NULL); DialogMessageButton button = dialog_message_show(bt->dialogs, bt->dialog_message); string_clear(pin_str); return button == DialogMessageButtonCenter; @@ -91,11 +91,16 @@ static void bt_battery_level_changed_callback(const void* _event, void* context) furi_assert(context); Bt* bt = context; + BtMessage message = {}; const PowerEvent* event = _event; if(event->type == PowerEventTypeBatteryLevelChanged) { - BtMessage message = { - .type = BtMessageTypeUpdateBatteryLevel, - .data.battery_level = event->data.battery_level}; + message.type = BtMessageTypeUpdateBatteryLevel; + message.data.battery_level = event->data.battery_level; + furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); + } else if( + event->type == PowerEventTypeStartCharging || event->type == PowerEventTypeFullyCharged || + event->type == PowerEventTypeStopCharging) { + message.type = BtMessageTypeUpdatePowerState; furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); } } @@ -167,7 +172,11 @@ static void bt_rpc_send_bytes_callback(void* context, uint8_t* bytes, size_t byt furi_assert(context); Bt* bt = context; - osEventFlagsClear(bt->rpc_event, BT_RPC_EVENT_ALL); + if(osEventFlagsGet(bt->rpc_event) & BT_RPC_EVENT_DISCONNECTED) { + // Early stop from sending if we're already disconnected + return; + } + osEventFlagsClear(bt->rpc_event, BT_RPC_EVENT_ALL & (~BT_RPC_EVENT_DISCONNECTED)); size_t bytes_sent = 0; while(bytes_sent < bytes_len) { size_t bytes_remain = bytes_len - bytes_sent; @@ -178,10 +187,14 @@ static void bt_rpc_send_bytes_callback(void* context, uint8_t* bytes, size_t byt furi_hal_bt_serial_tx(&bytes[bytes_sent], bytes_remain); bytes_sent += bytes_remain; } - uint32_t event_flag = - osEventFlagsWait(bt->rpc_event, BT_RPC_EVENT_ALL, osFlagsWaitAny, osWaitForever); + // We want BT_RPC_EVENT_DISCONNECTED to stick, so don't clear + uint32_t event_flag = osEventFlagsWait( + bt->rpc_event, BT_RPC_EVENT_ALL, osFlagsWaitAny | osFlagsNoClear, osWaitForever); if(event_flag & BT_RPC_EVENT_DISCONNECTED) { break; + } else { + // If we didn't get BT_RPC_EVENT_DISCONNECTED, then clear everything else + osEventFlagsClear(bt->rpc_event, BT_RPC_EVENT_ALL & (~BT_RPC_EVENT_DISCONNECTED)); } } } @@ -197,6 +210,8 @@ static bool bt_on_gap_event_callback(GapEvent event, void* context) { bt->status = BtStatusConnected; BtMessage message = {.type = BtMessageTypeUpdateStatus}; furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); + // Clear BT_RPC_EVENT_DISCONNECTED because it might be set from previous session + osEventFlagsClear(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED); if(bt->profile == BtProfileSerial) { // Open RPC session bt->rpc_session = rpc_session_open(bt->rpc); @@ -368,6 +383,8 @@ int32_t bt_srv() { } else if(message.type == BtMessageTypeUpdateBatteryLevel) { // Update battery level furi_hal_bt_update_battery_level(message.data.battery_level); + } else if(message.type == BtMessageTypeUpdatePowerState) { + furi_hal_bt_update_power_state(); } else if(message.type == BtMessageTypePinCodeShow) { // Display PIN code bt_pin_code_show(bt, message.data.pin_code); diff --git a/applications/bt/bt_service/bt_i.h b/applications/bt/bt_service/bt_i.h index 610c0905..caa45360 100644 --- a/applications/bt/bt_service/bt_i.h +++ b/applications/bt/bt_service/bt_i.h @@ -21,6 +21,7 @@ typedef enum { BtMessageTypeUpdateStatus, BtMessageTypeUpdateBatteryLevel, + BtMessageTypeUpdatePowerState, BtMessageTypePinCodeShow, BtMessageTypeKeysStorageUpdated, BtMessageTypeSetProfile, diff --git a/applications/debug_tools/file_browser_test/file_browser_app.c b/applications/debug_tools/file_browser_test/file_browser_app.c new file mode 100644 index 00000000..c9b63ecb --- /dev/null +++ b/applications/debug_tools/file_browser_test/file_browser_app.c @@ -0,0 +1,99 @@ +#include "assets_icons.h" +#include "file_browser_app_i.h" +#include "gui/modules/file_browser.h" +#include "m-string.h" +#include +#include +#include +#include + +static bool file_browser_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + FileBrowserApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool file_browser_app_back_event_callback(void* context) { + furi_assert(context); + FileBrowserApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void file_browser_app_tick_event_callback(void* context) { + furi_assert(context); + FileBrowserApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +FileBrowserApp* file_browser_app_alloc(char* arg) { + UNUSED(arg); + FileBrowserApp* app = malloc(sizeof(FileBrowserApp)); + + app->gui = furi_record_open("gui"); + app->dialogs = furi_record_open("dialogs"); + + app->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(app->view_dispatcher); + + app->scene_manager = scene_manager_alloc(&file_browser_scene_handlers, app); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, file_browser_app_tick_event_callback, 500); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, file_browser_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, file_browser_app_back_event_callback); + + app->widget = widget_alloc(); + + string_init(app->file_path); + app->file_browser = file_browser_alloc(app->file_path); + file_browser_configure(app->file_browser, "*", true, &I_badusb_10px, true); + + view_dispatcher_add_view( + app->view_dispatcher, FileBrowserAppViewStart, widget_get_view(app->widget)); + view_dispatcher_add_view( + app->view_dispatcher, FileBrowserAppViewResult, widget_get_view(app->widget)); + view_dispatcher_add_view( + app->view_dispatcher, FileBrowserAppViewBrowser, file_browser_get_view(app->file_browser)); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + scene_manager_next_scene(app->scene_manager, FileBrowserSceneStart); + + return app; +} + +void file_browser_app_free(FileBrowserApp* app) { + furi_assert(app); + + // Views + view_dispatcher_remove_view(app->view_dispatcher, FileBrowserAppViewStart); + view_dispatcher_remove_view(app->view_dispatcher, FileBrowserAppViewResult); + view_dispatcher_remove_view(app->view_dispatcher, FileBrowserAppViewBrowser); + widget_free(app->widget); + file_browser_free(app->file_browser); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + // Close records + furi_record_close("gui"); + furi_record_close("notification"); + furi_record_close("dialogs"); + + string_clear(app->file_path); + + free(app); +} + +int32_t file_browser_app(void* p) { + FileBrowserApp* file_browser_app = file_browser_app_alloc((char*)p); + + view_dispatcher_run(file_browser_app->view_dispatcher); + + file_browser_app_free(file_browser_app); + return 0; +} diff --git a/applications/debug_tools/file_browser_test/file_browser_app_i.h b/applications/debug_tools/file_browser_test/file_browser_app_i.h new file mode 100644 index 00000000..6e8412c9 --- /dev/null +++ b/applications/debug_tools/file_browser_test/file_browser_app_i.h @@ -0,0 +1,32 @@ +#pragma once + +#include "scenes/file_browser_scene.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct FileBrowserApp FileBrowserApp; + +struct FileBrowserApp { + Gui* gui; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + DialogsApp* dialogs; + Widget* widget; + FileBrowser* file_browser; + + string_t file_path; +}; + +typedef enum { + FileBrowserAppViewStart, + FileBrowserAppViewBrowser, + FileBrowserAppViewResult, +} FileBrowserAppView; diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene.c b/applications/debug_tools/file_browser_test/scenes/file_browser_scene.c new file mode 100644 index 00000000..72a6e84d --- /dev/null +++ b/applications/debug_tools/file_browser_test/scenes/file_browser_scene.c @@ -0,0 +1,30 @@ +#include "file_browser_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const file_browser_scene_on_enter_handlers[])(void*) = { +#include "file_browser_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 file_browser_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "file_browser_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 file_browser_scene_on_exit_handlers[])(void* context) = { +#include "file_browser_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers file_browser_scene_handlers = { + .on_enter_handlers = file_browser_scene_on_enter_handlers, + .on_event_handlers = file_browser_scene_on_event_handlers, + .on_exit_handlers = file_browser_scene_on_exit_handlers, + .scene_num = FileBrowserSceneNum, +}; diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene.h b/applications/debug_tools/file_browser_test/scenes/file_browser_scene.h new file mode 100644 index 00000000..d690fca9 --- /dev/null +++ b/applications/debug_tools/file_browser_test/scenes/file_browser_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) FileBrowserScene##id, +typedef enum { +#include "file_browser_scene_config.h" + FileBrowserSceneNum, +} FileBrowserScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers file_browser_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "file_browser_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 "file_browser_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 "file_browser_scene_config.h" +#undef ADD_SCENE diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene_browser.c b/applications/debug_tools/file_browser_test/scenes/file_browser_scene_browser.c new file mode 100644 index 00000000..ca16ad0d --- /dev/null +++ b/applications/debug_tools/file_browser_test/scenes/file_browser_scene_browser.c @@ -0,0 +1,43 @@ +#include "../file_browser_app_i.h" +#include "furi/check.h" +#include "furi/log.h" +#include "furi_hal.h" +#include "m-string.h" + +#define DEFAULT_PATH "/" +#define EXTENSION "*" + +bool file_browser_scene_browser_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + FileBrowserApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_next_scene(app->scene_manager, FileBrowserSceneResult); + consumed = true; + } else if(event.type == SceneManagerEventTypeTick) { + } + return consumed; +} + +static void file_browser_callback(void* context) { + FileBrowserApp* app = context; + furi_assert(app); + view_dispatcher_send_custom_event(app->view_dispatcher, SceneManagerEventTypeCustom); +} + +void file_browser_scene_browser_on_enter(void* context) { + FileBrowserApp* app = context; + + file_browser_set_callback(app->file_browser, file_browser_callback, app); + + file_browser_start(app->file_browser, app->file_path); + + view_dispatcher_switch_to_view(app->view_dispatcher, FileBrowserAppViewBrowser); +} + +void file_browser_scene_browser_on_exit(void* context) { + FileBrowserApp* app = context; + + file_browser_stop(app->file_browser); +} diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene_config.h b/applications/debug_tools/file_browser_test/scenes/file_browser_scene_config.h new file mode 100644 index 00000000..6597df3a --- /dev/null +++ b/applications/debug_tools/file_browser_test/scenes/file_browser_scene_config.h @@ -0,0 +1,3 @@ +ADD_SCENE(file_browser, start, Start) +ADD_SCENE(file_browser, browser, Browser) +ADD_SCENE(file_browser, result, Result) diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene_result.c b/applications/debug_tools/file_browser_test/scenes/file_browser_scene_result.c new file mode 100644 index 00000000..53576cef --- /dev/null +++ b/applications/debug_tools/file_browser_test/scenes/file_browser_scene_result.c @@ -0,0 +1,36 @@ +#include "../file_browser_app_i.h" +#include "furi_hal.h" +#include "m-string.h" + +void file_browser_scene_result_ok_callback(InputType type, void* context) { + furi_assert(context); + FileBrowserApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, type); +} + +bool file_browser_scene_result_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + //FileBrowserApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + } else if(event.type == SceneManagerEventTypeTick) { + } + return consumed; +} + +void file_browser_scene_result_on_enter(void* context) { + FileBrowserApp* app = context; + + widget_add_string_multiline_element( + app->widget, 64, 10, AlignCenter, AlignTop, FontSecondary, string_get_cstr(app->file_path)); + + view_dispatcher_switch_to_view(app->view_dispatcher, FileBrowserAppViewResult); +} + +void file_browser_scene_result_on_exit(void* context) { + UNUSED(context); + FileBrowserApp* app = context; + widget_reset(app->widget); +} diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene_start.c b/applications/debug_tools/file_browser_test/scenes/file_browser_scene_start.c new file mode 100644 index 00000000..bb71e83d --- /dev/null +++ b/applications/debug_tools/file_browser_test/scenes/file_browser_scene_start.c @@ -0,0 +1,44 @@ +#include "../file_browser_app_i.h" +#include "furi_hal.h" +#include "gui/modules/widget_elements/widget_element_i.h" + +static void + file_browser_scene_start_ok_callback(GuiButtonType result, InputType type, void* context) { + UNUSED(result); + furi_assert(context); + FileBrowserApp* app = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(app->view_dispatcher, type); + } +} + +bool file_browser_scene_start_on_event(void* context, SceneManagerEvent event) { + FileBrowserApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + string_set_str(app->file_path, "/any/badusb/demo_windows.txt"); + scene_manager_next_scene(app->scene_manager, FileBrowserSceneBrowser); + consumed = true; + } else if(event.type == SceneManagerEventTypeTick) { + } + return consumed; +} + +void file_browser_scene_start_on_enter(void* context) { + FileBrowserApp* app = context; + + widget_add_string_multiline_element( + app->widget, 64, 20, AlignCenter, AlignTop, FontSecondary, "Press OK to start"); + + widget_add_button_element( + app->widget, GuiButtonTypeCenter, "Ok", file_browser_scene_start_ok_callback, app); + + view_dispatcher_switch_to_view(app->view_dispatcher, FileBrowserAppViewStart); +} + +void file_browser_scene_start_on_exit(void* context) { + UNUSED(context); + FileBrowserApp* app = context; + widget_reset(app->widget); +} diff --git a/applications/desktop/helpers/pin_lock.c b/applications/desktop/helpers/pin_lock.c index 00ac4177..d63398d9 100644 --- a/applications/desktop/helpers/pin_lock.c +++ b/applications/desktop/helpers/pin_lock.c @@ -100,11 +100,12 @@ void desktop_pin_lock_init(DesktopSettings* settings) { } else { furi_hal_rtc_set_pin_fails(0); furi_hal_rtc_reset_flag(FuriHalRtcFlagLock); - furi_hal_usb_enable(); } if(desktop_pin_lock_is_locked()) { - furi_hal_usb_disable(); + Cli* cli = furi_record_open("cli"); + cli_session_close(cli); + furi_record_close("cli"); } } diff --git a/applications/desktop/views/desktop_view_locked.c b/applications/desktop/views/desktop_view_locked.c index 9f39c2ce..4b544988 100644 --- a/applications/desktop/views/desktop_view_locked.c +++ b/applications/desktop/views/desktop_view_locked.c @@ -125,7 +125,9 @@ static void desktop_view_locked_draw(Canvas* canvas, void* model) { canvas_set_font(canvas, FontSecondary); elements_bold_rounded_frame(canvas, 14, 2 + STATUS_BAR_Y_SHIFT, 99, 48); elements_multiline_text(canvas, 65, 20 + STATUS_BAR_Y_SHIFT, "To unlock\npress:"); - canvas_draw_icon(canvas, 65, 36 + STATUS_BAR_Y_SHIFT, &I_Back3_45x8); + canvas_draw_icon(canvas, 65, 36 + STATUS_BAR_Y_SHIFT, &I_Pin_back_arrow_10x8); + canvas_draw_icon(canvas, 80, 36 + STATUS_BAR_Y_SHIFT, &I_Pin_back_arrow_10x8); + canvas_draw_icon(canvas, 95, 36 + STATUS_BAR_Y_SHIFT, &I_Pin_back_arrow_10x8); canvas_draw_icon(canvas, 16, 7 + STATUS_BAR_Y_SHIFT, &I_WarningDolphin_45x42); canvas_draw_dot(canvas, 17, 61); } else if(view_state == DesktopViewLockedStateUnlockedHintShown) { diff --git a/applications/dialogs/dialogs.c b/applications/dialogs/dialogs.c index cf4a2ad6..8929dc11 100644 --- a/applications/dialogs/dialogs.c +++ b/applications/dialogs/dialogs.c @@ -1,6 +1,7 @@ +#include "dialogs/dialogs_message.h" #include "dialogs_i.h" #include "dialogs_api_lock.h" -#include "dialogs_module_file_select.h" +#include "dialogs_module_file_browser.h" #include "dialogs_module_message.h" static DialogsApp* dialogs_app_alloc() { @@ -13,9 +14,9 @@ static DialogsApp* dialogs_app_alloc() { static void dialogs_app_process_message(DialogsApp* app, DialogsAppMessage* message) { UNUSED(app); switch(message->command) { - case DialogsAppCommandFileOpen: + case DialogsAppCommandFileBrowser: message->return_data->bool_value = - dialogs_app_process_module_file_select(&message->data->file_select); + dialogs_app_process_module_file_browser(&message->data->file_browser); break; case DialogsAppCommandDialog: message->return_data->dialog_value = diff --git a/applications/dialogs/dialogs.h b/applications/dialogs/dialogs.h index 9c71c098..53606056 100644 --- a/applications/dialogs/dialogs.h +++ b/applications/dialogs/dialogs.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include "m-string.h" #ifdef __cplusplus extern "C" { @@ -10,25 +11,27 @@ extern "C" { typedef struct DialogsApp DialogsApp; -/****************** FILE SELECT ******************/ +/****************** FILE BROWSER ******************/ /** - * Shows and processes the file selection dialog + * Shows and processes the file browser dialog * @param context api pointer - * @param path path to directory + * @param result_path selected file path string pointer + * @param path preselected file path string pointer * @param extension file extension to be offered for selection - * @param selected_filename buffer where the selected filename will be saved - * @param selected_filename_size and the size of this buffer - * @param preselected_filename filename to be preselected + * @param skip_assets true - do not show assets folders + * @param icon file icon pointer, NULL for default icon + * @param hide_ext true - hide extensions for files * @return bool whether a file was selected */ -bool dialog_file_select_show( +bool dialog_file_browser_show( DialogsApp* context, - const char* path, + string_ptr result_path, + string_ptr path, const char* extension, - char* result, - uint8_t result_size, - const char* preselected_filename); + bool skip_assets, + const Icon* icon, + bool hide_ext); /****************** MESSAGE ******************/ diff --git a/applications/dialogs/dialogs_api.c b/applications/dialogs/dialogs_api.c index c4efb287..fab3a5ea 100644 --- a/applications/dialogs/dialogs_api.c +++ b/applications/dialogs/dialogs_api.c @@ -1,31 +1,36 @@ +#include "dialogs/dialogs_message.h" #include "dialogs_i.h" #include "dialogs_api_lock.h" +#include "m-string.h" -/****************** File select ******************/ +/****************** File browser ******************/ -bool dialog_file_select_show( +bool dialog_file_browser_show( DialogsApp* context, - const char* path, + string_ptr result_path, + string_ptr path, const char* extension, - char* result, - uint8_t result_size, - const char* preselected_filename) { + bool skip_assets, + const Icon* icon, + bool hide_ext) { FuriApiLock lock = API_LOCK_INIT_LOCKED(); furi_check(lock != NULL); DialogsAppData data = { - .file_select = { - .path = path, + .file_browser = { .extension = extension, - .result = result, - .result_size = result_size, - .preselected_filename = preselected_filename, + .result_path = result_path, + .file_icon = icon, + .hide_ext = hide_ext, + .skip_assets = skip_assets, + .preselected_filename = path, + }}; DialogsAppReturn return_data; DialogsAppMessage message = { .lock = lock, - .command = DialogsAppCommandFileOpen, + .command = DialogsAppCommandFileBrowser, .data = &data, .return_data = &return_data, }; diff --git a/applications/dialogs/dialogs_message.h b/applications/dialogs/dialogs_message.h index d7b5fabf..ccfbdece 100644 --- a/applications/dialogs/dialogs_message.h +++ b/applications/dialogs/dialogs_message.h @@ -2,25 +2,27 @@ #include #include "dialogs_i.h" #include "dialogs_api_lock.h" +#include "m-string.h" #ifdef __cplusplus extern "C" { #endif typedef struct { - const char* path; const char* extension; - char* result; - uint8_t result_size; - const char* preselected_filename; -} DialogsAppMessageDataFileSelect; + bool skip_assets; + bool hide_ext; + const Icon* file_icon; + string_ptr result_path; + string_ptr preselected_filename; +} DialogsAppMessageDataFileBrowser; typedef struct { const DialogMessage* message; } DialogsAppMessageDataDialog; typedef union { - DialogsAppMessageDataFileSelect file_select; + DialogsAppMessageDataFileBrowser file_browser; DialogsAppMessageDataDialog dialog; } DialogsAppData; @@ -30,7 +32,7 @@ typedef union { } DialogsAppReturn; typedef enum { - DialogsAppCommandFileOpen, + DialogsAppCommandFileBrowser, DialogsAppCommandDialog, } DialogsAppCommand; diff --git a/applications/dialogs/dialogs_module_file_browser.c b/applications/dialogs/dialogs_module_file_browser.c new file mode 100644 index 00000000..ecd0ca79 --- /dev/null +++ b/applications/dialogs/dialogs_module_file_browser.c @@ -0,0 +1,59 @@ +#include "dialogs_i.h" +#include "dialogs_api_lock.h" +#include "gui/modules/file_browser.h" + +typedef struct { + FuriApiLock lock; + bool result; +} DialogsAppFileBrowserContext; + +static void dialogs_app_file_browser_back_callback(void* context) { + furi_assert(context); + DialogsAppFileBrowserContext* file_browser_context = context; + file_browser_context->result = false; + API_LOCK_UNLOCK(file_browser_context->lock); +} + +static void dialogs_app_file_browser_callback(void* context) { + furi_assert(context); + DialogsAppFileBrowserContext* file_browser_context = context; + file_browser_context->result = true; + API_LOCK_UNLOCK(file_browser_context->lock); +} + +bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrowser* data) { + bool ret = false; + Gui* gui = furi_record_open("gui"); + + DialogsAppFileBrowserContext* file_browser_context = + malloc(sizeof(DialogsAppFileBrowserContext)); + file_browser_context->lock = API_LOCK_INIT_LOCKED(); + + ViewHolder* view_holder = view_holder_alloc(); + view_holder_attach_to_gui(view_holder, gui); + view_holder_set_back_callback( + view_holder, dialogs_app_file_browser_back_callback, file_browser_context); + + FileBrowser* file_browser = file_browser_alloc(data->result_path); + file_browser_set_callback( + file_browser, dialogs_app_file_browser_callback, file_browser_context); + file_browser_configure( + file_browser, data->extension, data->skip_assets, data->file_icon, data->hide_ext); + file_browser_start(file_browser, data->preselected_filename); + + view_holder_set_view(view_holder, file_browser_get_view(file_browser)); + view_holder_start(view_holder); + API_LOCK_WAIT_UNTIL_UNLOCK(file_browser_context->lock); + + ret = file_browser_context->result; + + view_holder_stop(view_holder); + view_holder_free(view_holder); + file_browser_stop(file_browser); + file_browser_free(file_browser); + API_LOCK_FREE(file_browser_context->lock); + free(file_browser_context); + furi_record_close("gui"); + + return ret; +} diff --git a/applications/dialogs/dialogs_module_file_select.h b/applications/dialogs/dialogs_module_file_browser.h similarity index 54% rename from applications/dialogs/dialogs_module_file_select.h rename to applications/dialogs/dialogs_module_file_browser.h index 749fe9c1..b6cbdf6a 100644 --- a/applications/dialogs/dialogs_module_file_select.h +++ b/applications/dialogs/dialogs_module_file_browser.h @@ -5,7 +5,7 @@ extern "C" { #endif -bool dialogs_app_process_module_file_select(const DialogsAppMessageDataFileSelect* data); +bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrowser* data); #ifdef __cplusplus } diff --git a/applications/dialogs/dialogs_module_file_select.c b/applications/dialogs/dialogs_module_file_select.c deleted file mode 100644 index f133c00a..00000000 --- a/applications/dialogs/dialogs_module_file_select.c +++ /dev/null @@ -1,59 +0,0 @@ -#include "dialogs_i.h" -#include "dialogs_api_lock.h" -#include - -typedef struct { - FuriApiLock lock; - bool result; -} DialogsAppFileSelectContext; - -static void dialogs_app_file_select_back_callback(void* context) { - furi_assert(context); - DialogsAppFileSelectContext* file_select_context = context; - file_select_context->result = false; - API_LOCK_UNLOCK(file_select_context->lock); -} - -static void dialogs_app_file_select_callback(bool result, void* context) { - furi_assert(context); - DialogsAppFileSelectContext* file_select_context = context; - file_select_context->result = result; - API_LOCK_UNLOCK(file_select_context->lock); -} - -bool dialogs_app_process_module_file_select(const DialogsAppMessageDataFileSelect* data) { - bool ret = false; - Gui* gui = furi_record_open("gui"); - - DialogsAppFileSelectContext* file_select_context = malloc(sizeof(DialogsAppFileSelectContext)); - file_select_context->lock = API_LOCK_INIT_LOCKED(); - - ViewHolder* view_holder = view_holder_alloc(); - view_holder_attach_to_gui(view_holder, gui); - view_holder_set_back_callback( - view_holder, dialogs_app_file_select_back_callback, file_select_context); - - FileSelect* file_select = file_select_alloc(); - file_select_set_callback(file_select, dialogs_app_file_select_callback, file_select_context); - file_select_set_filter(file_select, data->path, data->extension); - file_select_set_result_buffer(file_select, data->result, data->result_size); - file_select_init(file_select); - if(data->preselected_filename != NULL) { - file_select_set_selected_file(file_select, data->preselected_filename); - } - - view_holder_set_view(view_holder, file_select_get_view(file_select)); - view_holder_start(view_holder); - API_LOCK_WAIT_UNTIL_UNLOCK(file_select_context->lock); - - ret = file_select_context->result; - - view_holder_stop(view_holder); - view_holder_free(view_holder); - file_select_free(file_select); - API_LOCK_FREE(file_select_context->lock); - free(file_select_context); - furi_record_close("gui"); - - return ret; -} diff --git a/applications/dolphin/helpers/dolphin_state.c b/applications/dolphin/helpers/dolphin_state.c index 86287a2a..8a569392 100644 --- a/applications/dolphin/helpers/dolphin_state.c +++ b/applications/dolphin/helpers/dolphin_state.c @@ -72,19 +72,8 @@ bool dolphin_state_load(DolphinState* dolphin_state) { uint64_t dolphin_state_timestamp() { FuriHalRtcDateTime datetime; - struct tm current; - furi_hal_rtc_get_datetime(&datetime); - - current.tm_year = datetime.year - 1900; - current.tm_mday = datetime.day; - current.tm_mon = datetime.month - 1; - - current.tm_hour = datetime.hour; - current.tm_min = datetime.minute; - current.tm_sec = datetime.second; - - return mktime(¤t); + return furi_hal_rtc_datetime_to_timestamp(&datetime); } bool dolphin_state_is_levelup(uint32_t icounter) { diff --git a/applications/gpio/gpio_custom_event.h b/applications/gpio/gpio_custom_event.h index cae36035..2bf3e5a8 100644 --- a/applications/gpio/gpio_custom_event.h +++ b/applications/gpio/gpio_custom_event.h @@ -3,7 +3,7 @@ typedef enum { GpioStartEventOtgOff = 0, GpioStartEventOtgOn, - GpioStartEventManualConrol, + GpioStartEventManualControl, GpioStartEventUsbUart, GpioCustomEventErrorBack, diff --git a/applications/gpio/scenes/gpio_scene_start.c b/applications/gpio/scenes/gpio_scene_start.c index 4257ec39..41b74523 100644 --- a/applications/gpio/scenes/gpio_scene_start.c +++ b/applications/gpio/scenes/gpio_scene_start.c @@ -15,15 +15,15 @@ enum GpioOtg { }; const char* const gpio_otg_text[GpioOtgSettingsNum] = { - "Off", - "On", + "OFF", + "ON", }; static void gpio_scene_start_var_list_enter_callback(void* context, uint32_t index) { furi_assert(context); GpioApp* app = context; if(index == GpioItemTest) { - view_dispatcher_send_custom_event(app->view_dispatcher, GpioStartEventManualConrol); + view_dispatcher_send_custom_event(app->view_dispatcher, GpioStartEventManualControl); } else if(index == GpioItemUsbUart) { view_dispatcher_send_custom_event(app->view_dispatcher, GpioStartEventUsbUart); } @@ -82,7 +82,7 @@ bool gpio_scene_start_on_event(void* context, SceneManagerEvent event) { furi_hal_power_enable_otg(); } else if(event.event == GpioStartEventOtgOff) { furi_hal_power_disable_otg(); - } else if(event.event == GpioStartEventManualConrol) { + } else if(event.event == GpioStartEventManualControl) { scene_manager_set_scene_state(app->scene_manager, GpioSceneStart, GpioItemTest); scene_manager_next_scene(app->scene_manager, GpioSceneTest); } else if(event.event == GpioStartEventUsbUart) { diff --git a/applications/gui/modules/dialog_ex.c b/applications/gui/modules/dialog_ex.c index c01fb4c5..dee2a097 100755 --- a/applications/gui/modules/dialog_ex.c +++ b/applications/gui/modules/dialog_ex.c @@ -45,8 +45,8 @@ static void dialog_ex_view_draw_callback(Canvas* canvas, void* _model) { } // Draw header + canvas_set_font(canvas, FontPrimary); if(model->header.text != NULL) { - canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned( canvas, model->header.x, @@ -57,8 +57,8 @@ static void dialog_ex_view_draw_callback(Canvas* canvas, void* _model) { } // Draw text + canvas_set_font(canvas, FontSecondary); if(model->text.text != NULL) { - canvas_set_font(canvas, FontSecondary); elements_multiline_text_aligned( canvas, model->text.x, diff --git a/applications/gui/modules/file_browser.c b/applications/gui/modules/file_browser.c new file mode 100644 index 00000000..1cef1d07 --- /dev/null +++ b/applications/gui/modules/file_browser.c @@ -0,0 +1,534 @@ +#include "file_browser.h" +#include "assets_icons.h" +#include "cmsis_os2.h" +#include "file_browser_worker.h" +#include "furi/check.h" +#include "furi/common_defines.h" +#include "furi/log.h" +#include "furi_hal_resources.h" +#include "m-string.h" +#include +#include +#include +#include "toolbox/path.h" + +#define LIST_ITEMS 5u +#define MAX_LEN_PX 110 +#define FRAME_HEIGHT 12 +#define Y_OFFSET 3 + +#define ITEM_LIST_LEN_MAX 100 + +typedef enum { + BrowserItemTypeLoading, + BrowserItemTypeBack, + BrowserItemTypeFolder, + BrowserItemTypeFile, +} BrowserItemType; + +typedef struct { + string_t path; + BrowserItemType type; +} BrowserItem_t; + +static void BrowserItem_t_init(BrowserItem_t* obj) { + obj->type = BrowserItemTypeLoading; + string_init(obj->path); +} + +static void BrowserItem_t_init_set(BrowserItem_t* obj, const BrowserItem_t* src) { + obj->type = src->type; + string_init_set(obj->path, src->path); +} + +static void BrowserItem_t_set(BrowserItem_t* obj, const BrowserItem_t* src) { + obj->type = src->type; + string_set(obj->path, src->path); +} + +static void BrowserItem_t_clear(BrowserItem_t* obj) { + string_clear(obj->path); +} + +ARRAY_DEF( + items_array, + BrowserItem_t, + (INIT(API_2(BrowserItem_t_init)), + SET(API_6(BrowserItem_t_set)), + INIT_SET(API_6(BrowserItem_t_init_set)), + CLEAR(API_2(BrowserItem_t_clear)))) + +struct FileBrowser { + View* view; + BrowserWorker* worker; + const char* ext_filter; + bool skip_assets; + + FileBrowserCallback callback; + void* context; + + string_ptr result_path; +}; + +typedef struct { + items_array_t items; + + bool is_root; + bool folder_loading; + bool list_loading; + uint32_t item_cnt; + int32_t item_idx; + int32_t array_offset; + int32_t list_offset; + + const Icon* file_icon; + bool hide_ext; +} FileBrowserModel; + +static const Icon* BrowserItemIcons[] = { + [BrowserItemTypeLoading] = &I_loading_10px, + [BrowserItemTypeBack] = &I_back_10px, + [BrowserItemTypeFolder] = &I_dir_10px, + [BrowserItemTypeFile] = &I_unknown_10px, +}; + +static void file_browser_view_draw_callback(Canvas* canvas, void* _model); +static bool file_browser_view_input_callback(InputEvent* event, void* context); + +static void + browser_folder_open_cb(void* context, uint32_t item_cnt, int32_t file_idx, bool is_root); +static void browser_list_load_cb(void* context, uint32_t list_load_offset); +static void browser_list_item_cb(void* context, string_t item_path, bool is_folder, bool is_last); +static void browser_long_load_cb(void* context); + +FileBrowser* file_browser_alloc(string_ptr result_path) { + furi_assert(result_path); + FileBrowser* browser = malloc(sizeof(FileBrowser)); + browser->view = view_alloc(); + view_allocate_model(browser->view, ViewModelTypeLocking, sizeof(FileBrowserModel)); + view_set_context(browser->view, browser); + view_set_draw_callback(browser->view, file_browser_view_draw_callback); + view_set_input_callback(browser->view, file_browser_view_input_callback); + + browser->result_path = result_path; + + with_view_model( + browser->view, (FileBrowserModel * model) { + items_array_init(model->items); + return false; + }); + + return browser; +} + +void file_browser_free(FileBrowser* browser) { + furi_assert(browser); + + with_view_model( + browser->view, (FileBrowserModel * model) { + items_array_clear(model->items); + return false; + }); + + view_free(browser->view); + free(browser); +} + +View* file_browser_get_view(FileBrowser* browser) { + furi_assert(browser); + return browser->view; +} + +void file_browser_configure( + FileBrowser* browser, + const char* extension, + bool skip_assets, + const Icon* file_icon, + bool hide_ext) { + furi_assert(browser); + + browser->ext_filter = extension; + browser->skip_assets = skip_assets; + + with_view_model( + browser->view, (FileBrowserModel * model) { + model->file_icon = file_icon; + model->hide_ext = hide_ext; + return false; + }); +} + +void file_browser_start(FileBrowser* browser, string_t path) { + furi_assert(browser); + browser->worker = file_browser_worker_alloc(path, browser->ext_filter, browser->skip_assets); + file_browser_worker_set_callback_context(browser->worker, browser); + file_browser_worker_set_folder_callback(browser->worker, browser_folder_open_cb); + file_browser_worker_set_list_callback(browser->worker, browser_list_load_cb); + file_browser_worker_set_item_callback(browser->worker, browser_list_item_cb); + file_browser_worker_set_long_load_callback(browser->worker, browser_long_load_cb); +} + +void file_browser_stop(FileBrowser* browser) { + furi_assert(browser); + file_browser_worker_free(browser->worker); + with_view_model( + browser->view, (FileBrowserModel * model) { + items_array_reset(model->items); + model->item_cnt = 0; + model->item_idx = 0; + model->array_offset = 0; + model->list_offset = 0; + return false; + }); +} + +void file_browser_set_callback(FileBrowser* browser, FileBrowserCallback callback, void* context) { + browser->context = context; + browser->callback = callback; +} + +static bool browser_is_item_in_array(FileBrowserModel* model, uint32_t idx) { + size_t array_size = items_array_size(model->items); + + if((idx >= (uint32_t)model->array_offset + array_size) || + (idx < (uint32_t)model->array_offset)) { + return false; + } + return true; +} + +static bool browser_is_list_load_required(FileBrowserModel* model) { + size_t array_size = items_array_size(model->items); + uint32_t item_cnt = (model->is_root) ? model->item_cnt : model->item_cnt - 1; + + if((model->list_loading) || (array_size >= item_cnt)) { + return false; + } + + if((model->array_offset > 0) && + (model->item_idx < (model->array_offset + ITEM_LIST_LEN_MAX / 4))) { + return true; + } + + if(((model->array_offset + array_size) < item_cnt) && + (model->item_idx > (int32_t)(model->array_offset + array_size - ITEM_LIST_LEN_MAX / 4))) { + return true; + } + + return false; +} + +static void browser_update_offset(FileBrowser* browser) { + furi_assert(browser); + + with_view_model( + browser->view, (FileBrowserModel * model) { + uint16_t bounds = model->item_cnt > (LIST_ITEMS - 1) ? 2 : model->item_cnt; + + if((model->item_cnt > (LIST_ITEMS - 1)) && + (model->item_idx >= ((int32_t)model->item_cnt - 1))) { + model->list_offset = model->item_idx - (LIST_ITEMS - 1); + } else if(model->list_offset < model->item_idx - bounds) { + model->list_offset = CLAMP( + model->item_idx - (int32_t)(LIST_ITEMS - 2), + (int32_t)model->item_cnt - bounds, + 0); + } else if(model->list_offset > model->item_idx - bounds) { + model->list_offset = + CLAMP(model->item_idx - 1, (int32_t)model->item_cnt - bounds, 0); + } + + return false; + }); +} + +static void + browser_folder_open_cb(void* context, uint32_t item_cnt, int32_t file_idx, bool is_root) { + furi_assert(context); + FileBrowser* browser = (FileBrowser*)context; + + int32_t load_offset = 0; + + with_view_model( + browser->view, (FileBrowserModel * model) { + items_array_reset(model->items); + if(is_root) { + model->item_cnt = item_cnt; + model->item_idx = (file_idx > 0) ? file_idx : 0; + load_offset = + CLAMP(model->item_idx - ITEM_LIST_LEN_MAX / 2, (int32_t)model->item_cnt, 0); + } else { + model->item_cnt = item_cnt + 1; + model->item_idx = file_idx + 1; + load_offset = CLAMP( + model->item_idx - ITEM_LIST_LEN_MAX / 2 - 1, (int32_t)model->item_cnt - 1, 0); + } + model->array_offset = 0; + model->list_offset = 0; + model->is_root = is_root; + model->list_loading = true; + model->folder_loading = false; + return true; + }); + browser_update_offset(browser); + + file_browser_worker_load(browser->worker, load_offset, ITEM_LIST_LEN_MAX); +} + +static void browser_list_load_cb(void* context, uint32_t list_load_offset) { + furi_assert(context); + FileBrowser* browser = (FileBrowser*)context; + + BrowserItem_t back_item; + BrowserItem_t_init(&back_item); + back_item.type = BrowserItemTypeBack; + + with_view_model( + browser->view, (FileBrowserModel * model) { + items_array_reset(model->items); + model->array_offset = list_load_offset; + if(!model->is_root) { + if(list_load_offset == 0) { + items_array_push_back(model->items, back_item); + } else { + model->array_offset += 1; + } + } + return false; + }); + + BrowserItem_t_clear(&back_item); +} + +static void browser_list_item_cb(void* context, string_t item_path, bool is_folder, bool is_last) { + furi_assert(context); + FileBrowser* browser = (FileBrowser*)context; + + BrowserItem_t item; + + if(!is_last) { + BrowserItem_t_init(&item); + string_set(item.path, item_path); + item.type = (is_folder) ? BrowserItemTypeFolder : BrowserItemTypeFile; + + with_view_model( + browser->view, (FileBrowserModel * model) { + items_array_push_back(model->items, item); + return false; + }); + BrowserItem_t_clear(&item); + } else { + with_view_model( + browser->view, (FileBrowserModel * model) { + model->list_loading = false; + return true; + }); + } +} + +static void browser_long_load_cb(void* context) { + furi_assert(context); + FileBrowser* browser = (FileBrowser*)context; + + with_view_model( + browser->view, (FileBrowserModel * model) { + model->folder_loading = true; + return true; + }); +} + +static void browser_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_box( + canvas, 0, Y_OFFSET + idx * FRAME_HEIGHT, (scrollbar ? 122 : 127), FRAME_HEIGHT); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_dot(canvas, 0, Y_OFFSET + idx * FRAME_HEIGHT); + canvas_draw_dot(canvas, 1, Y_OFFSET + idx * FRAME_HEIGHT); + canvas_draw_dot(canvas, 0, (Y_OFFSET + idx * FRAME_HEIGHT) + 1); + + canvas_draw_dot(canvas, 0, (Y_OFFSET + idx * FRAME_HEIGHT) + (FRAME_HEIGHT - 1)); + canvas_draw_dot(canvas, scrollbar ? 121 : 126, Y_OFFSET + idx * FRAME_HEIGHT); + canvas_draw_dot( + canvas, scrollbar ? 121 : 126, (Y_OFFSET + idx * FRAME_HEIGHT) + (FRAME_HEIGHT - 1)); +} + +static void browser_draw_loading(Canvas* canvas, FileBrowserModel* model) { + uint8_t width = 49; + uint8_t height = 47; + uint8_t x = 128 / 2 - width / 2; + uint8_t y = 64 / 2 - height / 2; + + UNUSED(model); + + elements_bold_rounded_frame(canvas, x, y, width, height); + + canvas_set_font(canvas, FontSecondary); + elements_multiline_text(canvas, x + 7, y + 13, "Loading..."); + + canvas_draw_icon(canvas, x + 13, y + 19, &A_Loading_24); +} + +static void browser_draw_list(Canvas* canvas, FileBrowserModel* model) { + uint32_t array_size = items_array_size(model->items); + bool show_scrollbar = model->item_cnt > LIST_ITEMS; + + string_t filename; + string_init(filename); + + for(uint32_t i = 0; i < MIN(model->item_cnt, LIST_ITEMS); i++) { + int32_t idx = CLAMP((uint32_t)(i + model->list_offset), model->item_cnt, 0u); + + BrowserItemType item_type = BrowserItemTypeLoading; + + if(browser_is_item_in_array(model, idx)) { + BrowserItem_t* item = items_array_get( + model->items, CLAMP(idx - model->array_offset, (int32_t)(array_size - 1), 0)); + item_type = item->type; + path_extract_filename( + item->path, filename, (model->hide_ext) && (item_type == BrowserItemTypeFile)); + } else { + string_set_str(filename, "---"); + } + + if(item_type == BrowserItemTypeBack) { + string_set_str(filename, ". ."); + } + + elements_string_fit_width( + canvas, filename, (show_scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX)); + + if(model->item_idx == idx) { + browser_draw_frame(canvas, i, show_scrollbar); + } else { + canvas_set_color(canvas, ColorBlack); + } + + if((item_type == BrowserItemTypeFile) && (model->file_icon)) { + canvas_draw_icon(canvas, 2, Y_OFFSET + 1 + i * FRAME_HEIGHT, model->file_icon); + } else if(BrowserItemIcons[item_type] != NULL) { + canvas_draw_icon( + canvas, 2, Y_OFFSET + 1 + i * FRAME_HEIGHT, BrowserItemIcons[item_type]); + } + canvas_draw_str(canvas, 15, Y_OFFSET + 9 + i * FRAME_HEIGHT, string_get_cstr(filename)); + } + + if(show_scrollbar) { + elements_scrollbar_pos( + canvas, + 126, + Y_OFFSET, + canvas_height(canvas) - Y_OFFSET, + model->item_idx, + model->item_cnt); + } + + string_clear(filename); +} + +static void file_browser_view_draw_callback(Canvas* canvas, void* _model) { + FileBrowserModel* model = _model; + + if(model->folder_loading) { + browser_draw_loading(canvas, model); + } else { + browser_draw_list(canvas, model); + } +} + +static bool file_browser_view_input_callback(InputEvent* event, void* context) { + FileBrowser* browser = context; + furi_assert(browser); + bool consumed = false; + bool is_loading = false; + + with_view_model( + browser->view, (FileBrowserModel * model) { + is_loading = model->folder_loading; + return false; + }); + + if(is_loading) { + return false; + } else if(event->key == InputKeyUp || event->key == InputKeyDown) { + if(event->type == InputTypeShort || event->type == InputTypeRepeat) { + with_view_model( + browser->view, (FileBrowserModel * model) { + if(event->key == InputKeyUp) { + model->item_idx = + ((model->item_idx - 1) + model->item_cnt) % model->item_cnt; + if(browser_is_list_load_required(model)) { + model->list_loading = true; + int32_t load_offset = CLAMP( + model->item_idx - ITEM_LIST_LEN_MAX / 4 * 3, + (int32_t)model->item_cnt - ITEM_LIST_LEN_MAX, + 0); + file_browser_worker_load( + browser->worker, load_offset, ITEM_LIST_LEN_MAX); + } + } else if(event->key == InputKeyDown) { + model->item_idx = (model->item_idx + 1) % model->item_cnt; + if(browser_is_list_load_required(model)) { + model->list_loading = true; + int32_t load_offset = CLAMP( + model->item_idx - ITEM_LIST_LEN_MAX / 4 * 1, + (int32_t)model->item_cnt - ITEM_LIST_LEN_MAX, + 0); + file_browser_worker_load( + browser->worker, load_offset, ITEM_LIST_LEN_MAX); + } + } + return true; + }); + browser_update_offset(browser); + consumed = true; + } + } else if(event->key == InputKeyOk) { + if(event->type == InputTypeShort) { + BrowserItem_t* selected_item = NULL; + int32_t select_index = 0; + with_view_model( + browser->view, (FileBrowserModel * model) { + if(browser_is_item_in_array(model, model->item_idx)) { + selected_item = + items_array_get(model->items, model->item_idx - model->array_offset); + select_index = model->item_idx; + if((!model->is_root) && (select_index > 0)) { + select_index -= 1; + } + } + return false; + }); + + if(selected_item) { + if(selected_item->type == BrowserItemTypeBack) { + file_browser_worker_folder_exit(browser->worker); + } else if(selected_item->type == BrowserItemTypeFolder) { + file_browser_worker_folder_enter( + browser->worker, selected_item->path, select_index); + } else if(selected_item->type == BrowserItemTypeFile) { + string_set(browser->result_path, selected_item->path); + if(browser->callback) { + browser->callback(browser->context); + } + } + } + consumed = true; + } + } else if(event->key == InputKeyLeft) { + if(event->type == InputTypeShort) { + bool is_root = false; + with_view_model( + browser->view, (FileBrowserModel * model) { + is_root = model->is_root; + return false; + }); + if(!is_root) { + file_browser_worker_folder_exit(browser->worker); + } + consumed = true; + } + } + + return consumed; +} diff --git a/applications/gui/modules/file_browser.h b/applications/gui/modules/file_browser.h new file mode 100644 index 00000000..ebc64509 --- /dev/null +++ b/applications/gui/modules/file_browser.h @@ -0,0 +1,39 @@ +/** + * @file file_browser.h + * GUI: FileBrowser view module API + */ + +#pragma once + +#include "m-string.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct FileBrowser FileBrowser; +typedef void (*FileBrowserCallback)(void* context); + +FileBrowser* file_browser_alloc(string_ptr result_path); + +void file_browser_free(FileBrowser* browser); + +View* file_browser_get_view(FileBrowser* browser); + +void file_browser_configure( + FileBrowser* browser, + const char* extension, + bool skip_assets, + const Icon* file_icon, + bool hide_ext); + +void file_browser_start(FileBrowser* browser, string_t path); + +void file_browser_stop(FileBrowser* browser); + +void file_browser_set_callback(FileBrowser* browser, FileBrowserCallback callback, void* context); + +#ifdef __cplusplus +} +#endif diff --git a/applications/gui/modules/file_browser_worker.c b/applications/gui/modules/file_browser_worker.c new file mode 100644 index 00000000..93baba00 --- /dev/null +++ b/applications/gui/modules/file_browser_worker.c @@ -0,0 +1,408 @@ +#include "file_browser_worker.h" +#include "furi/check.h" +#include "furi/common_defines.h" +#include "m-string.h" +#include "storage/filesystem_api_defines.h" +#include +#include +#include +#include +#include +#include "toolbox/path.h" + +#define TAG "BrowserWorker" + +#define ASSETS_DIR "assets" +#define BROWSER_ROOT "/any" +#define FILE_NAME_LEN_MAX 256 +#define LONG_LOAD_THRESHOLD 100 + +typedef enum { + WorkerEvtStop = (1 << 0), + WorkerEvtLoad = (1 << 1), + WorkerEvtFolderEnter = (1 << 2), + WorkerEvtFolderExit = (1 << 3), +} WorkerEvtFlags; + +#define WORKER_FLAGS_ALL \ + (WorkerEvtStop | WorkerEvtLoad | WorkerEvtFolderEnter | WorkerEvtFolderExit) + +ARRAY_DEF(idx_last_array, int32_t) + +struct BrowserWorker { + FuriThread* thread; + + string_t filter_extension; + string_t path_next; + int32_t item_sel_idx; + uint32_t load_offset; + uint32_t load_count; + bool skip_assets; + idx_last_array_t idx_last; + + void* cb_ctx; + BrowserWorkerFolderOpenCallback folder_cb; + BrowserWorkerListLoadCallback list_load_cb; + BrowserWorkerListItemCallback list_item_cb; + BrowserWorkerLongLoadCallback long_load_cb; +}; + +static bool browser_path_is_file(string_t path) { + bool state = false; + FileInfo file_info; + Storage* storage = furi_record_open("storage"); + if(storage_common_stat(storage, string_get_cstr(path), &file_info) == FSE_OK) { + if((file_info.flags & FSF_DIRECTORY) == 0) { + state = true; + } + } + furi_record_close("storage"); + return state; +} + +static bool browser_path_trim(string_t path) { + bool is_root = false; + size_t filename_start = string_search_rchar(path, '/'); + string_left(path, filename_start); + if((string_empty_p(path)) || (filename_start == STRING_FAILURE)) { + string_set_str(path, BROWSER_ROOT); + is_root = true; + } + return is_root; +} + +static bool browser_filter_by_name(BrowserWorker* browser, string_t name, bool is_folder) { + if(is_folder) { + // Skip assets folders (if enabled) + if(browser->skip_assets) { + return ((string_cmp_str(name, ASSETS_DIR) == 0) ? (false) : (true)); + } else { + return true; + } + } else { + // Filter files by extension + if((string_empty_p(browser->filter_extension)) || + (string_cmp_str(browser->filter_extension, "*") == 0)) { + return true; + } + if(string_end_with_string_p(name, browser->filter_extension)) { + return true; + } + } + return false; +} + +static bool browser_folder_check_and_switch(string_t path) { + FileInfo file_info; + Storage* storage = furi_record_open("storage"); + bool is_root = false; + while(1) { + // Check if folder is existing and navigate back if not + if(storage_common_stat(storage, string_get_cstr(path), &file_info) == FSE_OK) { + if(file_info.flags & FSF_DIRECTORY) { + break; + } + } + if(is_root) { + break; + } + is_root = browser_path_trim(path); + } + furi_record_close("storage"); + return is_root; +} + +static bool browser_folder_init( + BrowserWorker* browser, + string_t path, + string_t filename, + uint32_t* item_cnt, + int32_t* file_idx) { + bool state = false; + FileInfo file_info; + uint32_t total_files_cnt = 0; + + Storage* storage = furi_record_open("storage"); + File* directory = storage_file_alloc(storage); + + char name_temp[FILE_NAME_LEN_MAX]; + string_t name_str; + string_init(name_str); + + *item_cnt = 0; + *file_idx = -1; + + if(storage_dir_open(directory, string_get_cstr(path))) { + state = true; + while(1) { + if(!storage_dir_read(directory, &file_info, name_temp, FILE_NAME_LEN_MAX)) { + break; + } + if((storage_file_get_error(directory) == FSE_OK) && (name_temp[0] != '\0')) { + total_files_cnt++; + string_set_str(name_str, name_temp); + if(browser_filter_by_name(browser, name_str, (file_info.flags & FSF_DIRECTORY))) { + if(!string_empty_p(filename)) { + if(string_cmp(name_str, filename) == 0) { + *file_idx = *item_cnt; + } + } + (*item_cnt)++; + } + if(total_files_cnt == LONG_LOAD_THRESHOLD) { + // There are too many files in folder and counting them will take some time - send callback to app + if(browser->long_load_cb) { + browser->long_load_cb(browser->cb_ctx); + } + } + } + } + } + + string_clear(name_str); + + storage_dir_close(directory); + storage_file_free(directory); + + furi_record_close("storage"); + + return state; +} + +static bool + browser_folder_load(BrowserWorker* browser, string_t path, uint32_t offset, uint32_t count) { + FileInfo file_info; + + Storage* storage = furi_record_open("storage"); + File* directory = storage_file_alloc(storage); + + char name_temp[FILE_NAME_LEN_MAX]; + string_t name_str; + string_init(name_str); + + uint32_t items_cnt = 0; + + do { + if(!storage_dir_open(directory, string_get_cstr(path))) { + break; + } + + items_cnt = 0; + while(items_cnt < offset) { + if(!storage_dir_read(directory, &file_info, name_temp, FILE_NAME_LEN_MAX)) { + break; + } + if(storage_file_get_error(directory) == FSE_OK) { + string_set_str(name_str, name_temp); + if(browser_filter_by_name(browser, name_str, (file_info.flags & FSF_DIRECTORY))) { + items_cnt++; + } + } else { + break; + } + } + if(items_cnt != offset) { + break; + } + + if(browser->list_load_cb) { + browser->list_load_cb(browser->cb_ctx, offset); + } + + items_cnt = 0; + while(items_cnt < count) { + if(!storage_dir_read(directory, &file_info, name_temp, FILE_NAME_LEN_MAX)) { + break; + } + if(storage_file_get_error(directory) == FSE_OK) { + string_set_str(name_str, name_temp); + if(browser_filter_by_name(browser, name_str, (file_info.flags & FSF_DIRECTORY))) { + string_printf(name_str, "%s/%s", string_get_cstr(path), name_temp); + if(browser->list_item_cb) { + browser->list_item_cb( + browser->cb_ctx, name_str, (file_info.flags & FSF_DIRECTORY), false); + } + items_cnt++; + } + } else { + break; + } + } + if(browser->list_item_cb) { + browser->list_item_cb(browser->cb_ctx, NULL, false, true); + } + } while(0); + + string_clear(name_str); + + storage_dir_close(directory); + storage_file_free(directory); + + furi_record_close("storage"); + + return (items_cnt == count); +} + +static int32_t browser_worker(void* context) { + BrowserWorker* browser = (BrowserWorker*)context; + furi_assert(browser); + FURI_LOG_D(TAG, "Start"); + + uint32_t items_cnt = 0; + string_t path; + string_init_set_str(path, BROWSER_ROOT); + browser->item_sel_idx = -1; + + // If start path is a path to the file - try finding index of this file in a folder + string_t filename; + string_init(filename); + if(browser_path_is_file(browser->path_next)) { + path_extract_filename(browser->path_next, filename, false); + } + + osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtFolderEnter); + + while(1) { + uint32_t flags = osThreadFlagsWait(WORKER_FLAGS_ALL, osFlagsWaitAny, osWaitForever); + furi_assert((flags & osFlagsError) == 0); + + if(flags & WorkerEvtFolderEnter) { + string_set(path, browser->path_next); + bool is_root = browser_folder_check_and_switch(path); + + // Push previous selected item index to history array + idx_last_array_push_back(browser->idx_last, browser->item_sel_idx); + + int32_t file_idx = 0; + browser_folder_init(browser, path, filename, &items_cnt, &file_idx); + FURI_LOG_D( + TAG, + "Enter folder: %s items: %u idx: %d", + string_get_cstr(path), + items_cnt, + file_idx); + if(browser->folder_cb) { + browser->folder_cb(browser->cb_ctx, items_cnt, file_idx, is_root); + } + string_reset(filename); + } + + if(flags & WorkerEvtFolderExit) { + browser_path_trim(path); + bool is_root = browser_folder_check_and_switch(path); + + int32_t file_idx = 0; + browser_folder_init(browser, path, filename, &items_cnt, &file_idx); + if(idx_last_array_size(browser->idx_last) > 0) { + // Pop previous selected item index from history array + idx_last_array_pop_back(&file_idx, browser->idx_last); + } + FURI_LOG_D( + TAG, "Exit to: %s items: %u idx: %d", string_get_cstr(path), items_cnt, file_idx); + if(browser->folder_cb) { + browser->folder_cb(browser->cb_ctx, items_cnt, file_idx, is_root); + } + } + + if(flags & WorkerEvtLoad) { + FURI_LOG_D(TAG, "Load offset: %u cnt: %u", browser->load_offset, browser->load_count); + browser_folder_load(browser, path, browser->load_offset, browser->load_count); + } + + if(flags & WorkerEvtStop) { + break; + } + } + + string_clear(filename); + string_clear(path); + + FURI_LOG_D(TAG, "End"); + return 0; +} + +BrowserWorker* file_browser_worker_alloc(string_t path, const char* filter_ext, bool skip_assets) { + BrowserWorker* browser = malloc(sizeof(BrowserWorker)); + + idx_last_array_init(browser->idx_last); + + string_init_set_str(browser->filter_extension, filter_ext); + browser->skip_assets = skip_assets; + string_init_set(browser->path_next, path); + + browser->thread = furi_thread_alloc(); + furi_thread_set_name(browser->thread, "BrowserWorker"); + furi_thread_set_stack_size(browser->thread, 2048); + furi_thread_set_context(browser->thread, browser); + furi_thread_set_callback(browser->thread, browser_worker); + furi_thread_start(browser->thread); + + return browser; +} + +void file_browser_worker_free(BrowserWorker* browser) { + furi_assert(browser); + + osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtStop); + furi_thread_join(browser->thread); + furi_thread_free(browser->thread); + + string_clear(browser->filter_extension); + string_clear(browser->path_next); + + idx_last_array_clear(browser->idx_last); + + free(browser); +} + +void file_browser_worker_set_callback_context(BrowserWorker* browser, void* context) { + furi_assert(browser); + browser->cb_ctx = context; +} + +void file_browser_worker_set_folder_callback( + BrowserWorker* browser, + BrowserWorkerFolderOpenCallback cb) { + furi_assert(browser); + browser->folder_cb = cb; +} + +void file_browser_worker_set_list_callback( + BrowserWorker* browser, + BrowserWorkerListLoadCallback cb) { + furi_assert(browser); + browser->list_load_cb = cb; +} + +void file_browser_worker_set_item_callback( + BrowserWorker* browser, + BrowserWorkerListItemCallback cb) { + furi_assert(browser); + browser->list_item_cb = cb; +} + +void file_browser_worker_set_long_load_callback( + BrowserWorker* browser, + BrowserWorkerLongLoadCallback cb) { + furi_assert(browser); + browser->long_load_cb = cb; +} + +void file_browser_worker_folder_enter(BrowserWorker* browser, string_t path, int32_t item_idx) { + furi_assert(browser); + string_set(browser->path_next, path); + browser->item_sel_idx = item_idx; + osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtFolderEnter); +} + +void file_browser_worker_folder_exit(BrowserWorker* browser) { + furi_assert(browser); + osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtFolderExit); +} + +void file_browser_worker_load(BrowserWorker* browser, uint32_t offset, uint32_t count) { + furi_assert(browser); + browser->load_offset = offset; + browser->load_count = count; + osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtLoad); +} diff --git a/applications/gui/modules/file_browser_worker.h b/applications/gui/modules/file_browser_worker.h new file mode 100644 index 00000000..b0d360a3 --- /dev/null +++ b/applications/gui/modules/file_browser_worker.h @@ -0,0 +1,55 @@ +#pragma once + +#include "m-string.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct BrowserWorker BrowserWorker; +typedef void (*BrowserWorkerFolderOpenCallback)( + void* context, + uint32_t item_cnt, + int32_t file_idx, + bool is_root); +typedef void (*BrowserWorkerListLoadCallback)(void* context, uint32_t list_load_offset); +typedef void (*BrowserWorkerListItemCallback)( + void* context, + string_t item_path, + bool is_folder, + bool is_last); +typedef void (*BrowserWorkerLongLoadCallback)(void* context); + +BrowserWorker* file_browser_worker_alloc(string_t path, const char* filter_ext, bool skip_assets); + +void file_browser_worker_free(BrowserWorker* browser); + +void file_browser_worker_set_callback_context(BrowserWorker* browser, void* context); + +void file_browser_worker_set_folder_callback( + BrowserWorker* browser, + BrowserWorkerFolderOpenCallback cb); + +void file_browser_worker_set_list_callback( + BrowserWorker* browser, + BrowserWorkerListLoadCallback cb); + +void file_browser_worker_set_item_callback( + BrowserWorker* browser, + BrowserWorkerListItemCallback cb); + +void file_browser_worker_set_long_load_callback( + BrowserWorker* browser, + BrowserWorkerLongLoadCallback cb); + +void file_browser_worker_folder_enter(BrowserWorker* browser, string_t path, int32_t item_idx); + +void file_browser_worker_folder_exit(BrowserWorker* browser); + +void file_browser_worker_load(BrowserWorker* browser, uint32_t offset, uint32_t count); + +#ifdef __cplusplus +} +#endif diff --git a/applications/gui/modules/file_select.c b/applications/gui/modules/file_select.c deleted file mode 100644 index 14541196..00000000 --- a/applications/gui/modules/file_select.c +++ /dev/null @@ -1,475 +0,0 @@ -#include "file_select.h" -#include -#include -#include - -#define FILENAME_COUNT 4 - -struct FileSelect { - // public - View* view; - Storage* fs_api; - const char* path; - const char* extension; - - bool init_completed; - - FileSelectCallback callback; - void* context; - - char* buffer; - uint8_t buffer_size; -}; - -typedef struct { - string_t filename[FILENAME_COUNT]; - uint8_t position; - - uint16_t first_file_index; - uint16_t file_count; - -} FileSelectModel; - -bool file_select_fill_strings(FileSelect* file_select); -bool file_select_fill_count(FileSelect* file_select); -static bool file_select_init_inner(FileSelect* file_select); - -static void file_select_draw_callback(Canvas* canvas, void* _model) { - FileSelectModel* model = _model; - - string_t string_buff; - const uint8_t item_height = 16; - const uint8_t item_width = 123; - const uint8_t text_max_width = 115; - - canvas_clear(canvas); - canvas_set_font(canvas, FontSecondary); - - if(model->file_count) { - for(uint8_t i = 0; i < MIN(FILENAME_COUNT, model->file_count); i++) { - if(i == model->position) { - canvas_set_color(canvas, ColorBlack); - canvas_draw_box(canvas, 0, (i * item_height) + 1, item_width, item_height - 2); - - canvas_set_color(canvas, ColorWhite); - canvas_draw_dot(canvas, 0, (i * item_height) + 1); - canvas_draw_dot(canvas, 0, (i * item_height) + item_height - 2); - canvas_draw_dot(canvas, item_width - 1, (i * item_height) + 1); - canvas_draw_dot(canvas, item_width - 1, (i * item_height) + item_height - 2); - } else { - canvas_set_color(canvas, ColorBlack); - } - - string_init_set(string_buff, model->filename[i]); - elements_string_fit_width(canvas, string_buff, text_max_width); - canvas_draw_str( - canvas, 6, (i * item_height) + item_height - 4, string_get_cstr(string_buff)); - - string_clear(string_buff); - } - } else { - canvas_draw_str(canvas, 6, item_height, "Empty folder"); - } - elements_scrollbar(canvas, model->first_file_index + model->position, model->file_count); -} - -static bool file_select_input_callback(InputEvent* event, void* context) { - FileSelect* file_select = (FileSelect*)context; - bool consumed = false; - - if((event->type == InputTypeShort) | (event->type == InputTypeRepeat)) { - if(!file_select->init_completed) { - if(!file_select_init_inner(file_select)) { - file_select->callback(false, file_select->context); - } - } else if(event->key == InputKeyUp) { - with_view_model( - file_select->view, (FileSelectModel * model) { - if(model->position == 0) { - if(model->first_file_index == 0) { - // wrap - int16_t max_first_file_index = model->file_count - FILENAME_COUNT; - model->position = MIN(FILENAME_COUNT - 1, model->file_count - 1); - model->first_file_index = - max_first_file_index < 0 ? 0 : max_first_file_index; - } else { - model->first_file_index--; - } - } else if(model->position == 1) { - if(model->first_file_index == 0) { - model->position--; - } else { - model->first_file_index--; - } - } else { - model->position--; - } - return true; - }); - consumed = true; - if(!file_select_fill_strings(file_select)) { - file_select->callback(false, file_select->context); - } - } else if(event->key == InputKeyDown) { - with_view_model( - file_select->view, (FileSelectModel * model) { - uint16_t max_first_file_index = model->file_count > FILENAME_COUNT ? - model->file_count - FILENAME_COUNT : - 0; - - if(model->position >= MIN(FILENAME_COUNT - 1, model->file_count - 1)) { - if(model->first_file_index >= max_first_file_index) { - // wrap - model->position = 0; - model->first_file_index = 0; - } else { - model->first_file_index++; - } - } else if(model->position >= (FILENAME_COUNT - 2)) { - if(model->first_file_index >= max_first_file_index) { - model->position++; - } else { - model->first_file_index++; - } - } else { - model->position++; - } - return true; - }); - consumed = true; - if(!file_select_fill_strings(file_select)) { - file_select->callback(false, file_select->context); - } - } else if(event->key == InputKeyOk) { - if(file_select->callback != NULL) { - size_t files = 0; - if(file_select->buffer) { - with_view_model( - file_select->view, (FileSelectModel * model) { - files = model->file_count; - strlcpy( - file_select->buffer, - string_get_cstr(model->filename[model->position]), - file_select->buffer_size); - - return false; - }); - }; - if(files > 0) { - file_select->callback(true, file_select->context); - } - } - consumed = true; - } - } - - return consumed; -} - -static bool file_select_init_inner(FileSelect* file_select) { - bool result = false; - if(file_select->path && file_select->extension && file_select->fs_api) { - if(file_select_fill_count(file_select)) { - if(file_select_fill_strings(file_select)) { - file_select->init_completed = true; - result = true; - } - } - } - - return result; -} - -FileSelect* file_select_alloc() { - FileSelect* file_select = malloc(sizeof(FileSelect)); - file_select->view = view_alloc(); - file_select->fs_api = furi_record_open("storage"); - - view_set_context(file_select->view, file_select); - view_allocate_model(file_select->view, ViewModelTypeLockFree, sizeof(FileSelectModel)); - view_set_draw_callback(file_select->view, file_select_draw_callback); - view_set_input_callback(file_select->view, file_select_input_callback); - - with_view_model( - file_select->view, (FileSelectModel * model) { - for(uint8_t i = 0; i < FILENAME_COUNT; i++) { - string_init(model->filename[i]); - } - - model->first_file_index = 0; - model->file_count = 0; - return false; - }); - - return file_select; -} - -void file_select_free(FileSelect* file_select) { - furi_assert(file_select); - with_view_model( - file_select->view, (FileSelectModel * model) { - for(uint8_t i = 0; i < FILENAME_COUNT; i++) { - string_clear(model->filename[i]); - } - return false; - }); - view_free(file_select->view); - free(file_select); - furi_record_close("storage"); -} - -View* file_select_get_view(FileSelect* file_select) { - furi_assert(file_select); - return file_select->view; -} - -void file_select_set_callback(FileSelect* file_select, FileSelectCallback callback, void* context) { - file_select->context = context; - file_select->callback = callback; -} - -void file_select_set_filter(FileSelect* file_select, const char* path, const char* extension) { - furi_assert(file_select); - file_select->path = path; - file_select->extension = extension; -} - -void file_select_set_result_buffer(FileSelect* file_select, char* buffer, uint8_t buffer_size) { - file_select->buffer = buffer; - file_select->buffer_size = buffer_size; - - if(file_select->buffer) { - strlcpy(file_select->buffer, "", file_select->buffer_size); - } -} - -bool file_select_init(FileSelect* file_select) { - if(!file_select_init_inner(file_select)) { - file_select->callback(false, file_select->context); - return false; - } else { - return true; - } -} - -static bool filter_file(FileSelect* file_select, FileInfo* file_info, char* name) { - bool result = false; - - if(!(file_info->flags & FSF_DIRECTORY)) { - if(strcmp(file_select->extension, "*") == 0) { - result = true; - } else if(strstr(name, file_select->extension) != NULL) { - result = true; - } - } - - return result; -} - -bool file_select_fill_strings(FileSelect* file_select) { - furi_assert(file_select); - furi_assert(file_select->fs_api); - furi_assert(file_select->path); - furi_assert(file_select->extension); - - FileInfo file_info; - File* directory = storage_file_alloc(file_select->fs_api); - - uint8_t string_counter = 0; - uint16_t file_counter = 0; - const uint8_t name_length = 100; - char* name = malloc(name_length); - uint16_t first_file_index = 0; - - with_view_model( - file_select->view, (FileSelectModel * model) { - first_file_index = model->first_file_index; - return false; - }); - - if(!storage_dir_open(directory, file_select->path)) { - storage_dir_close(directory); - storage_file_free(directory); - free(name); - return true; - } - - while(1) { - if(!storage_dir_read(directory, &file_info, name, name_length)) { - break; - } - - if(storage_file_get_error(directory) == FSE_OK) { - if(filter_file(file_select, &file_info, name)) { - if(file_counter >= first_file_index) { - with_view_model( - file_select->view, (FileSelectModel * model) { - string_set_str(model->filename[string_counter], name); - - if(strcmp(file_select->extension, "*") != 0) { - string_replace_all_str( - model->filename[string_counter], file_select->extension, ""); - } - - return true; - }); - string_counter++; - - if(string_counter >= FILENAME_COUNT) { - break; - } - } - file_counter++; - } - } else { - storage_dir_close(directory); - storage_file_free(directory); - free(name); - return false; - } - } - - storage_dir_close(directory); - storage_file_free(directory); - free(name); - return true; -} - -bool file_select_fill_count(FileSelect* file_select) { - furi_assert(file_select); - furi_assert(file_select->fs_api); - furi_assert(file_select->path); - furi_assert(file_select->extension); - - FileInfo file_info; - File* directory = storage_file_alloc(file_select->fs_api); - - uint16_t file_counter = 0; - const uint8_t name_length = 100; - char* name = malloc(name_length); - - if(!storage_dir_open(directory, file_select->path)) { - storage_dir_close(directory); - storage_file_free(directory); - free(name); - return true; - } - - while(1) { - if(!storage_dir_read(directory, &file_info, name, name_length)) { - break; - } - - if(storage_file_get_error(directory) == FSE_OK) { - if(filter_file(file_select, &file_info, name)) { - file_counter++; - } - } else { - storage_dir_close(directory); - storage_file_free(directory); - free(name); - return false; - } - } - - with_view_model( - file_select->view, (FileSelectModel * model) { - model->file_count = file_counter; - return false; - }); - - storage_dir_close(directory); - storage_file_free(directory); - free(name); - return true; -} - -void file_select_set_selected_file_internal(FileSelect* file_select, const char* filename) { - furi_assert(file_select); - furi_assert(filename); - furi_assert(file_select->fs_api); - furi_assert(file_select->path); - furi_assert(file_select->extension); - - if(strlen(filename) == 0) return; - - FileInfo file_info; - File* directory = storage_file_alloc(file_select->fs_api); - - const uint8_t name_length = 100; - char* name = malloc(name_length); - uint16_t file_position = 0; - bool file_found = false; - - string_t filename_str; - string_init_set_str(filename_str, filename); - if(strcmp(file_select->extension, "*") != 0) { - string_cat_str(filename_str, file_select->extension); - } - - if(!storage_dir_open(directory, file_select->path)) { - string_clear(filename_str); - storage_dir_close(directory); - storage_file_free(directory); - free(name); - return; - } - - while(1) { - if(!storage_dir_read(directory, &file_info, name, name_length)) { - break; - } - - if(storage_file_get_error(directory) == FSE_OK) { - if(filter_file(file_select, &file_info, name)) { - if(strcmp(string_get_cstr(filename_str), name) == 0) { - file_found = true; - break; - } - - file_position++; - } - } else { - string_clear(filename_str); - storage_dir_close(directory); - storage_file_free(directory); - free(name); - return; - } - } - - if(file_found) { - with_view_model( - file_select->view, (FileSelectModel * model) { - uint16_t max_first_file_index = - model->file_count > FILENAME_COUNT ? model->file_count - FILENAME_COUNT : 0; - - model->first_file_index = file_position; - - if(model->first_file_index > 0) { - model->first_file_index -= 1; - } - - if(model->first_file_index >= max_first_file_index) { - model->first_file_index = max_first_file_index; - } - - model->position = file_position - model->first_file_index; - - return true; - }); - } - - string_clear(filename_str); - storage_dir_close(directory); - storage_file_free(directory); - free(name); -} - -void file_select_set_selected_file(FileSelect* file_select, const char* filename) { - file_select_set_selected_file_internal(file_select, filename); - - if(!file_select_fill_strings(file_select)) { - file_select->callback(false, file_select->context); - } -} diff --git a/applications/gui/modules/file_select.h b/applications/gui/modules/file_select.h deleted file mode 100644 index ed3d5b60..00000000 --- a/applications/gui/modules/file_select.h +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @file file_select.h - * GUI: FileSelect view module API - */ - -#pragma once - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct FileSelect FileSelect; - -typedef void (*FileSelectCallback)(bool result, void* context); - -FileSelect* file_select_alloc(); - -void file_select_free(FileSelect* file_select); -View* file_select_get_view(FileSelect* file_select); - -void file_select_set_callback(FileSelect* file_select, FileSelectCallback callback, void* context); -void file_select_set_filter(FileSelect* file_select, const char* path, const char* extension); -void file_select_set_result_buffer(FileSelect* file_select, char* buffer, uint8_t buffer_size); -bool file_select_init(FileSelect* file_select); -void file_select_set_selected_file(FileSelect* file_select, const char* filename); - -#ifdef __cplusplus -} -#endif diff --git a/applications/gui/modules/validators.c b/applications/gui/modules/validators.c index 242dbe68..546423d0 100644 --- a/applications/gui/modules/validators.c +++ b/applications/gui/modules/validators.c @@ -3,7 +3,7 @@ #include "applications/storage/storage.h" struct ValidatorIsFile { - const char* app_path_folder; + char* app_path_folder; const char* app_extension; char* current_name; }; @@ -40,7 +40,7 @@ ValidatorIsFile* validator_is_file_alloc_init( const char* current_name) { ValidatorIsFile* instance = malloc(sizeof(ValidatorIsFile)); - instance->app_path_folder = app_path_folder; + instance->app_path_folder = strdup(app_path_folder); instance->app_extension = app_extension; instance->current_name = strdup(current_name); @@ -49,6 +49,7 @@ ValidatorIsFile* validator_is_file_alloc_init( void validator_is_file_free(ValidatorIsFile* instance) { furi_assert(instance); + free(instance->app_path_folder); free(instance->current_name); free(instance); } diff --git a/applications/ibutton/ibutton.c b/applications/ibutton/ibutton.c index a38f077f..8713919b 100644 --- a/applications/ibutton/ibutton.c +++ b/applications/ibutton/ibutton.c @@ -1,7 +1,8 @@ #include "ibutton.h" +#include "assets_icons.h" #include "ibutton_i.h" #include "ibutton/scenes/ibutton_scene.h" - +#include "m-string.h" #include #include @@ -85,6 +86,8 @@ void ibutton_tick_event_callback(void* context) { iButton* ibutton_alloc() { iButton* ibutton = malloc(sizeof(iButton)); + string_init(ibutton->file_path); + ibutton->scene_manager = scene_manager_alloc(&ibutton_scene_handlers, ibutton); ibutton->view_dispatcher = view_dispatcher_alloc(); @@ -176,49 +179,28 @@ void ibutton_free(iButton* ibutton) { ibutton_worker_free(ibutton->key_worker); ibutton_key_free(ibutton->key); + string_clear(ibutton->file_path); + free(ibutton); } bool ibutton_file_select(iButton* ibutton) { - bool success = dialog_file_select_show( + bool success = dialog_file_browser_show( ibutton->dialogs, - IBUTTON_APP_FOLDER, + ibutton->file_path, + ibutton->file_path, IBUTTON_APP_EXTENSION, - ibutton->file_name, - IBUTTON_FILE_NAME_SIZE, - ibutton_key_get_name_p(ibutton->key)); + true, + &I_ibutt_10px, + true); if(success) { - string_t key_str; - string_init_printf( - key_str, "%s/%s%s", IBUTTON_APP_FOLDER, ibutton->file_name, IBUTTON_APP_EXTENSION); - success = ibutton_load_key_data(ibutton, key_str); - - if(success) { - ibutton_key_set_name(ibutton->key, ibutton->file_name); - } - - string_clear(key_str); + success = ibutton_load_key_data(ibutton, ibutton->file_path); } return success; } -bool ibutton_load_key(iButton* ibutton, const char* key_name) { - string_t key_path; - string_init_set_str(key_path, key_name); - - const bool success = ibutton_load_key_data(ibutton, key_path); - - if(success) { - path_extract_filename_no_ext(key_name, key_path); - ibutton_key_set_name(ibutton->key, string_get_cstr(key_path)); - } - - string_clear(key_path); - return success; -} - bool ibutton_save_key(iButton* ibutton, const char* key_name) { // Create ibutton directory if necessary ibutton_make_app_folder(ibutton); @@ -226,27 +208,23 @@ bool ibutton_save_key(iButton* ibutton, const char* key_name) { FlipperFormat* file = flipper_format_file_alloc(ibutton->storage); iButtonKey* key = ibutton->key; - string_t key_file_name; bool result = false; - string_init(key_file_name); do { - // First remove key if it was saved (we rename the key) - if(!ibutton_delete_key(ibutton)) break; + // Check if we has old key + if(string_end_with_str_p(ibutton->file_path, IBUTTON_APP_EXTENSION)) { + // First remove old key + ibutton_delete_key(ibutton); - // Save the key - ibutton_key_set_name(key, key_name); + // Remove old key name from path + size_t filename_start = string_search_rchar(ibutton->file_path, '/'); + string_left(ibutton->file_path, filename_start); + } - // Set full file name, for new key - string_printf( - key_file_name, - "%s/%s%s", - IBUTTON_APP_FOLDER, - ibutton_key_get_name_p(key), - IBUTTON_APP_EXTENSION); + string_cat_printf(ibutton->file_path, "/%s%s", key_name, IBUTTON_APP_EXTENSION); // Open file for write - if(!flipper_format_file_open_always(file, string_get_cstr(key_file_name))) break; + if(!flipper_format_file_open_always(file, string_get_cstr(ibutton->file_path))) break; // Write header if(!flipper_format_write_header_cstr(file, IBUTTON_APP_FILE_TYPE, 1)) break; @@ -271,8 +249,6 @@ bool ibutton_save_key(iButton* ibutton, const char* key_name) { flipper_format_free(file); - string_clear(key_file_name); - if(!result) { dialog_message_show_storage_error(ibutton->dialogs, "Cannot save\nkey file"); } @@ -281,17 +257,8 @@ bool ibutton_save_key(iButton* ibutton, const char* key_name) { } bool ibutton_delete_key(iButton* ibutton) { - string_t file_name; bool result = false; - - string_init_printf( - file_name, - "%s/%s%s", - IBUTTON_APP_FOLDER, - ibutton_key_get_name_p(ibutton->key), - IBUTTON_APP_EXTENSION); - result = storage_simply_remove(ibutton->storage, string_get_cstr(file_name)); - string_clear(file_name); + result = storage_simply_remove(ibutton->storage, string_get_cstr(ibutton->file_path)); return result; } @@ -335,8 +302,17 @@ int32_t ibutton_app(void* p) { ibutton_make_app_folder(ibutton); - if(p && ibutton_load_key(ibutton, (const char*)p)) { - // TODO: Display an error if the key from p could not be loaded + bool key_loaded = false; + + if(p) { + string_set_str(ibutton->file_path, (const char*)p); + if(ibutton_load_key_data(ibutton, ibutton->file_path)) { + key_loaded = true; + // TODO: Display an error if the key from p could not be loaded + } + } + + if(key_loaded) { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneEmulate); } else { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneStart); diff --git a/applications/ibutton/ibutton_i.h b/applications/ibutton/ibutton_i.h index 36857fd6..e66712be 100644 --- a/applications/ibutton/ibutton_i.h +++ b/applications/ibutton/ibutton_i.h @@ -41,7 +41,7 @@ struct iButton { iButtonWorker* key_worker; iButtonKey* key; - char file_name[IBUTTON_FILE_NAME_SIZE]; + string_t file_path; char text_store[IBUTTON_TEXT_STORE_SIZE + 1]; Submenu* submenu; @@ -74,7 +74,6 @@ typedef enum { } iButtonNotificationMessage; bool ibutton_file_select(iButton* ibutton); -bool ibutton_load_key(iButton* ibutton, const char* key_name); bool ibutton_save_key(iButton* ibutton, const char* key_name); bool ibutton_delete_key(iButton* ibutton); void ibutton_text_store_set(iButton* ibutton, const char* text, ...); diff --git a/applications/ibutton/scenes/ibutton_scene_add_type.c b/applications/ibutton/scenes/ibutton_scene_add_type.c index db129295..273330e7 100644 --- a/applications/ibutton/scenes/ibutton_scene_add_type.c +++ b/applications/ibutton/scenes/ibutton_scene_add_type.c @@ -1,4 +1,5 @@ #include "../ibutton_i.h" +#include "m-string.h" enum SubmenuIndex { SubmenuIndexCyfral, @@ -44,7 +45,7 @@ bool ibutton_scene_add_type_on_event(void* context, SceneManagerEvent event) { furi_crash("Unknown key type"); } - ibutton_key_set_name(key, ""); + string_set_str(ibutton->file_path, IBUTTON_APP_FOLDER); ibutton_key_clear_data(key); scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddValue); } diff --git a/applications/ibutton/scenes/ibutton_scene_delete_confirm.c b/applications/ibutton/scenes/ibutton_scene_delete_confirm.c index 73ea97cc..51f1f279 100644 --- a/applications/ibutton/scenes/ibutton_scene_delete_confirm.c +++ b/applications/ibutton/scenes/ibutton_scene_delete_confirm.c @@ -1,4 +1,5 @@ #include "../ibutton_i.h" +#include static void ibutton_scene_delete_confirm_widget_callback( GuiButtonType result, @@ -16,7 +17,11 @@ void ibutton_scene_delete_confirm_on_enter(void* context) { iButtonKey* key = ibutton->key; const uint8_t* key_data = ibutton_key_get_data_p(key); - ibutton_text_store_set(ibutton, "\e#Delete %s?\e#", ibutton_key_get_name_p(key)); + string_t key_name; + string_init(key_name); + path_extract_filename(ibutton->file_path, key_name, true); + + ibutton_text_store_set(ibutton, "\e#Delete %s?\e#", string_get_cstr(key_name)); widget_add_text_box_element( widget, 0, 0, 128, 27, AlignCenter, AlignCenter, ibutton->text_store, false); widget_add_button_element( @@ -62,6 +67,8 @@ void ibutton_scene_delete_confirm_on_enter(void* context) { widget, 64, 33, AlignCenter, AlignBottom, FontSecondary, ibutton->text_store); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); + + string_clear(key_name); } bool ibutton_scene_delete_confirm_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/ibutton/scenes/ibutton_scene_emulate.c b/applications/ibutton/scenes/ibutton_scene_emulate.c index 8ffe73b6..59022397 100644 --- a/applications/ibutton/scenes/ibutton_scene_emulate.c +++ b/applications/ibutton/scenes/ibutton_scene_emulate.c @@ -1,5 +1,6 @@ #include "../ibutton_i.h" #include +#include static void ibutton_scene_emulate_callback(void* context, bool emulated) { iButton* ibutton = context; @@ -15,14 +16,19 @@ void ibutton_scene_emulate_on_enter(void* context) { iButtonKey* key = ibutton->key; const uint8_t* key_data = ibutton_key_get_data_p(key); - const char* key_name = ibutton_key_get_name_p(key); + + string_t key_name; + string_init(key_name); + if(string_end_with_str_p(ibutton->file_path, IBUTTON_APP_EXTENSION)) { + path_extract_filename(ibutton->file_path, key_name, true); + } uint8_t line_count = 2; DOLPHIN_DEED(DolphinDeedIbuttonEmulate); // check that stored key has name - if(strcmp(key_name, "") != 0) { - ibutton_text_store_set(ibutton, "emulating\n%s", key_name); + if(!string_empty_p(key_name)) { + ibutton_text_store_set(ibutton, "emulating\n%s", string_get_cstr(key_name)); line_count = 2; } else { // if not, show key data @@ -77,6 +83,8 @@ void ibutton_scene_emulate_on_enter(void* context) { ibutton_worker_emulate_set_callback( ibutton->key_worker, ibutton_scene_emulate_callback, ibutton); ibutton_worker_emulate_start(ibutton->key_worker, key); + + string_clear(key_name); } bool ibutton_scene_emulate_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/ibutton/scenes/ibutton_scene_info.c b/applications/ibutton/scenes/ibutton_scene_info.c index 5b0af1d8..bd364ada 100644 --- a/applications/ibutton/scenes/ibutton_scene_info.c +++ b/applications/ibutton/scenes/ibutton_scene_info.c @@ -1,4 +1,5 @@ #include "../ibutton_i.h" +#include void ibutton_scene_info_on_enter(void* context) { iButton* ibutton = context; @@ -7,7 +8,11 @@ void ibutton_scene_info_on_enter(void* context) { const uint8_t* key_data = ibutton_key_get_data_p(key); - ibutton_text_store_set(ibutton, "%s", ibutton_key_get_name_p(key)); + string_t key_name; + string_init(key_name); + path_extract_filename(ibutton->file_path, key_name, true); + + ibutton_text_store_set(ibutton, "%s", string_get_cstr(key_name)); widget_add_text_box_element( widget, 0, 0, 128, 28, AlignCenter, AlignCenter, ibutton->text_store, false); @@ -46,6 +51,8 @@ void ibutton_scene_info_on_enter(void* context) { widget, 64, 35, AlignCenter, AlignBottom, FontPrimary, ibutton->text_store); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); + + string_clear(key_name); } bool ibutton_scene_info_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/ibutton/scenes/ibutton_scene_read.c b/applications/ibutton/scenes/ibutton_scene_read.c index a25f27e6..0cc0a8df 100644 --- a/applications/ibutton/scenes/ibutton_scene_read.c +++ b/applications/ibutton/scenes/ibutton_scene_read.c @@ -18,7 +18,7 @@ void ibutton_scene_read_on_enter(void* context) { popup_set_icon(popup, 0, 5, &I_DolphinWait_61x59); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); - ibutton_key_set_name(key, ""); + string_set_str(ibutton->file_path, IBUTTON_APP_FOLDER); ibutton_worker_read_set_callback(worker, ibutton_scene_read_callback, ibutton); ibutton_worker_read_start(worker, key); diff --git a/applications/ibutton/scenes/ibutton_scene_read_crc_error.c b/applications/ibutton/scenes/ibutton_scene_read_crc_error.c index 4df96d64..28d59d2d 100644 --- a/applications/ibutton/scenes/ibutton_scene_read_crc_error.c +++ b/applications/ibutton/scenes/ibutton_scene_read_crc_error.c @@ -61,11 +61,7 @@ void ibutton_scene_read_crc_error_on_exit(void* context) { ibutton_text_store_clear(ibutton); - dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); - dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); - dialog_ex_set_left_button_text(dialog_ex, NULL); - dialog_ex_set_result_callback(dialog_ex, NULL); - dialog_ex_set_context(dialog_ex, NULL); + dialog_ex_reset(dialog_ex); ibutton_notification_message(ibutton, iButtonNotificationMessageRedOff); } diff --git a/applications/ibutton/scenes/ibutton_scene_read_not_key_error.c b/applications/ibutton/scenes/ibutton_scene_read_not_key_error.c index 76f14dae..45fbefe8 100644 --- a/applications/ibutton/scenes/ibutton_scene_read_not_key_error.c +++ b/applications/ibutton/scenes/ibutton_scene_read_not_key_error.c @@ -62,11 +62,7 @@ void ibutton_scene_read_not_key_error_on_exit(void* context) { ibutton_text_store_clear(ibutton); - dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); - dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); - dialog_ex_set_left_button_text(dialog_ex, NULL); - dialog_ex_set_result_callback(dialog_ex, NULL); - dialog_ex_set_context(dialog_ex, NULL); + dialog_ex_reset(dialog_ex); ibutton_notification_message(ibutton, iButtonNotificationMessageRedOff); } diff --git a/applications/ibutton/scenes/ibutton_scene_read_success.c b/applications/ibutton/scenes/ibutton_scene_read_success.c index c3a13478..1c2bcdd2 100644 --- a/applications/ibutton/scenes/ibutton_scene_read_success.c +++ b/applications/ibutton/scenes/ibutton_scene_read_success.c @@ -76,12 +76,7 @@ void ibutton_scene_read_success_on_exit(void* context) { ibutton_text_store_clear(ibutton); - dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); - dialog_ex_set_left_button_text(dialog_ex, NULL); - dialog_ex_set_right_button_text(dialog_ex, NULL); - dialog_ex_set_result_callback(dialog_ex, NULL); - dialog_ex_set_context(dialog_ex, NULL); - dialog_ex_set_icon(dialog_ex, 0, 0, NULL); + dialog_ex_reset(dialog_ex); ibutton_notification_message(ibutton, iButtonNotificationMessageGreenOff); } diff --git a/applications/ibutton/scenes/ibutton_scene_save_name.c b/applications/ibutton/scenes/ibutton_scene_save_name.c index b1baf6af..6caf5d2d 100644 --- a/applications/ibutton/scenes/ibutton_scene_save_name.c +++ b/applications/ibutton/scenes/ibutton_scene_save_name.c @@ -1,5 +1,7 @@ #include "../ibutton_i.h" +#include "m-string.h" #include +#include static void ibutton_scene_save_name_text_input_callback(void* context) { iButton* ibutton = context; @@ -10,13 +12,17 @@ void ibutton_scene_save_name_on_enter(void* context) { iButton* ibutton = context; TextInput* text_input = ibutton->text_input; - const char* key_name = ibutton_key_get_name_p(ibutton->key); - const bool key_name_is_empty = !strcmp(key_name, ""); + string_t key_name; + string_init(key_name); + if(string_end_with_str_p(ibutton->file_path, IBUTTON_APP_EXTENSION)) { + path_extract_filename(ibutton->file_path, key_name, true); + } + const bool key_name_is_empty = string_empty_p(key_name); if(key_name_is_empty) { set_random_name(ibutton->text_store, IBUTTON_TEXT_STORE_SIZE); } else { - ibutton_text_store_set(ibutton, "%s", key_name); + ibutton_text_store_set(ibutton, "%s", string_get_cstr(key_name)); } text_input_set_header_text(text_input, "Name the key"); @@ -28,11 +34,19 @@ void ibutton_scene_save_name_on_enter(void* context) { IBUTTON_KEY_NAME_SIZE, key_name_is_empty); - ValidatorIsFile* validator_is_file = - validator_is_file_alloc_init(IBUTTON_APP_FOLDER, IBUTTON_APP_EXTENSION, key_name); + string_t folder_path; + string_init(folder_path); + + path_extract_dirname(string_get_cstr(ibutton->file_path), folder_path); + + ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( + string_get_cstr(folder_path), IBUTTON_APP_EXTENSION, string_get_cstr(key_name)); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewTextInput); + + string_clear(key_name); + string_clear(folder_path); } bool ibutton_scene_save_name_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/ibutton/scenes/ibutton_scene_start.c b/applications/ibutton/scenes/ibutton_scene_start.c index a3141f5a..cc8af983 100644 --- a/applications/ibutton/scenes/ibutton_scene_start.c +++ b/applications/ibutton/scenes/ibutton_scene_start.c @@ -36,6 +36,7 @@ bool ibutton_scene_start_on_event(void* context, SceneManagerEvent event) { if(event.event == SubmenuIndexRead) { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneRead); } else if(event.event == SubmenuIndexSaved) { + string_set_str(ibutton->file_path, IBUTTON_APP_FOLDER); scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSelectKey); } else if(event.event == SubmenuIndexAdd) { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddType); diff --git a/applications/ibutton/scenes/ibutton_scene_write.c b/applications/ibutton/scenes/ibutton_scene_write.c index 35e45d83..4ce19408 100644 --- a/applications/ibutton/scenes/ibutton_scene_write.c +++ b/applications/ibutton/scenes/ibutton_scene_write.c @@ -1,4 +1,6 @@ #include "../ibutton_i.h" +#include "m-string.h" +#include "toolbox/path.h" typedef enum { iButtonSceneWriteStateDefault, @@ -17,13 +19,18 @@ void ibutton_scene_write_on_enter(void* context) { iButtonWorker* worker = ibutton->key_worker; const uint8_t* key_data = ibutton_key_get_data_p(key); - const char* key_name = ibutton_key_get_name_p(key); + + string_t key_name; + string_init(key_name); + if(string_end_with_str_p(ibutton->file_path, IBUTTON_APP_EXTENSION)) { + path_extract_filename(ibutton->file_path, key_name, true); + } uint8_t line_count = 2; // check that stored key has name - if(strcmp(key_name, "") != 0) { - ibutton_text_store_set(ibutton, "writing\n%s", key_name); + if(!string_empty_p(key_name)) { + ibutton_text_store_set(ibutton, "writing\n%s", string_get_cstr(key_name)); line_count = 2; } else { // if not, show key data @@ -79,6 +86,8 @@ void ibutton_scene_write_on_enter(void* context) { ibutton_worker_write_set_callback(worker, ibutton_scene_write_callback, ibutton); ibutton_worker_write_start(worker, key); + + string_clear(key_name); } bool ibutton_scene_write_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/infrared/cli/infrared_cli.cpp b/applications/infrared/cli/infrared_cli.cpp index b60e4599..79ab987d 100644 --- a/applications/infrared/cli/infrared_cli.cpp +++ b/applications/infrared/cli/infrared_cli.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -86,7 +85,7 @@ static void infrared_cli_print_usage(void) { } printf("\r\n"); printf("\tRaw format:\r\n"); - printf("\tir_tx RAW F: DC: ...\r\n"); + printf("\tir tx RAW F: DC: ...\r\n"); printf( "\tFrequency (%d - %d), Duty cycle (0 - 100), max 512 samples\r\n", INFRARED_MIN_FREQUENCY, @@ -178,7 +177,7 @@ static void infrared_cli_start_ir(Cli* cli, string_t args, void* context) { break; } if(string_get_cstr(args)[size] == ' ') { - string_right(args, size); + string_right(args, size + 1); break; } } diff --git a/applications/infrared/infrared_app.cpp b/applications/infrared/infrared_app.cpp index 5dc3b311..1ac859d1 100644 --- a/applications/infrared/infrared_app.cpp +++ b/applications/infrared/infrared_app.cpp @@ -1,4 +1,5 @@ #include "infrared_app.h" +#include "m-string.h" #include #include #include @@ -12,20 +13,18 @@ int32_t InfraredApp::run(void* args) { bool exit = false; if(args) { - std::string path = static_cast(args); - std::string remote_name(path, path.find_last_of('/') + 1, path.size()); - auto last_dot = remote_name.find_last_of('.'); - if(last_dot != std::string::npos) { - remote_name.erase(last_dot); - path.erase(path.find_last_of('/')); - bool result = remote_manager.load(path, remote_name); + string_t path; + string_init_set_str(path, (char*)args); + if(string_end_with_str_p(path, InfraredApp::infrared_extension)) { + bool result = remote_manager.load(path); if(result) { current_scene = InfraredApp::Scene::Remote; } else { - printf("Failed to load remote \'%s\'\r\n", remote_name.c_str()); + printf("Failed to load remote \'%s\'\r\n", string_get_cstr(path)); return -1; } } + string_clear(path); } scenes[current_scene]->on_enter(this); @@ -51,6 +50,7 @@ int32_t InfraredApp::run(void* args) { InfraredApp::InfraredApp() { furi_check(InfraredAppRemoteManager::max_button_name_length < get_text_store_size()); + string_init_set_str(file_path, InfraredApp::infrared_directory); notification = static_cast(furi_record_open("notification")); dialogs = static_cast(furi_record_open("dialogs")); infrared_worker = infrared_worker_alloc(); @@ -60,6 +60,7 @@ InfraredApp::~InfraredApp() { infrared_worker_free(infrared_worker); furi_record_close("notification"); furi_record_close("dialogs"); + string_clear(file_path); for(auto& [key, scene] : scenes) delete scene; } diff --git a/applications/infrared/infrared_app.h b/applications/infrared/infrared_app.h index e0db7c51..1cb8b661 100644 --- a/applications/infrared/infrared_app.h +++ b/applications/infrared/infrared_app.h @@ -251,6 +251,8 @@ public: /** Main class destructor, deinitializes all critical objects */ ~InfraredApp(); + string_t file_path; + /** Path to Infrared directory */ static constexpr const char* infrared_directory = "/any/infrared"; /** Infrared files extension (remote files and universal databases) */ diff --git a/applications/infrared/infrared_app_remote_manager.cpp b/applications/infrared/infrared_app_remote_manager.cpp index 4fd64553..faeccb39 100644 --- a/applications/infrared/infrared_app_remote_manager.cpp +++ b/applications/infrared/infrared_app_remote_manager.cpp @@ -1,3 +1,5 @@ +#include "m-string.h" +#include "storage/filesystem_api_defines.h" #include #include "infrared_app_remote_manager.h" #include "infrared/helpers/infrared_parser.h" @@ -11,44 +13,58 @@ #include #include #include "infrared_app.h" +#include static const char* default_remote_name = "remote"; -std::string InfraredAppRemoteManager::make_full_name( - const std::string& path, - const std::string& remote_name) const { - return std::string("") + path + "/" + remote_name + InfraredApp::infrared_extension; -} - -std::string InfraredAppRemoteManager::find_vacant_remote_name(const std::string& name) { - std::string result_name; +void InfraredAppRemoteManager::find_vacant_remote_name(string_t name, string_t path) { Storage* storage = static_cast(furi_record_open("storage")); - FS_Error error = storage_common_stat( - storage, make_full_name(InfraredApp::infrared_directory, name).c_str(), NULL); + string_t base_path; + string_init_set(base_path, path); - if(error == FSE_NOT_EXIST) { - result_name = name; - } else if(error != FSE_OK) { - result_name = std::string(); - } else { + if(string_end_with_str_p(base_path, InfraredApp::infrared_extension)) { + size_t filename_start = string_search_rchar(base_path, '/'); + string_left(base_path, filename_start); + } + + string_printf( + base_path, + "%s/%s%s", + string_get_cstr(path), + string_get_cstr(name), + InfraredApp::infrared_extension); + + FS_Error error = storage_common_stat(storage, string_get_cstr(base_path), NULL); + + if(error == FSE_OK) { /* if suggested name is occupied, try another one (name2, name3, etc) */ + size_t dot = string_search_rchar(base_path, '.'); + string_left(base_path, dot); + + string_t path_temp; + string_init(path_temp); + uint32_t i = 1; - std::string new_name; do { - new_name = make_full_name(InfraredApp::infrared_directory, name + std::to_string(++i)); - error = storage_common_stat(storage, new_name.c_str(), NULL); + string_printf( + path_temp, + "%s%u%s", + string_get_cstr(base_path), + ++i, + InfraredApp::infrared_extension); + error = storage_common_stat(storage, string_get_cstr(path_temp), NULL); } while(error == FSE_OK); + string_clear(path_temp); + if(error == FSE_NOT_EXIST) { - result_name = name + std::to_string(i); - } else { - result_name = std::string(); + string_cat_printf(name, "%u", i); } } + string_clear(base_path); furi_record_close("storage"); - return result_name; } bool InfraredAppRemoteManager::add_button(const char* button_name, const InfraredAppSignal& signal) { @@ -61,12 +77,23 @@ bool InfraredAppRemoteManager::add_remote_with_button( const InfraredAppSignal& signal) { furi_check(button_name != nullptr); - auto new_name = find_vacant_remote_name(default_remote_name); - if(new_name.empty()) { - return false; - } + string_t new_name; + string_init_set_str(new_name, default_remote_name); + + string_t new_path; + string_init_set_str(new_path, InfraredApp::infrared_directory); + + find_vacant_remote_name(new_name, new_path); + + string_cat_printf( + new_path, "/%s%s", string_get_cstr(new_name), InfraredApp::infrared_extension); + + remote = std::make_unique(new_path); + remote->name = std::string(string_get_cstr(new_name)); + + string_clear(new_path); + string_clear(new_name); - remote = std::make_unique(InfraredApp::infrared_directory, new_name); return add_button(button_name, signal); } @@ -93,8 +120,7 @@ const InfraredAppSignal& InfraredAppRemoteManager::get_button_data(size_t index) bool InfraredAppRemoteManager::delete_remote() { Storage* storage = static_cast(furi_record_open("storage")); - FS_Error error = - storage_common_remove(storage, make_full_name(remote->path, remote->name).c_str()); + FS_Error error = storage_common_remove(storage, string_get_cstr(remote->path)); reset_remote(); furi_record_close("storage"); @@ -128,22 +154,33 @@ std::string InfraredAppRemoteManager::get_remote_name() { bool InfraredAppRemoteManager::rename_remote(const char* str) { furi_check(str != nullptr); furi_check(remote.get() != nullptr); + furi_check(!string_empty_p(remote->path)); if(!remote->name.compare(str)) { return true; } - auto new_name = find_vacant_remote_name(str); - if(new_name.empty()) { - return false; + string_t new_name; + string_init_set_str(new_name, str); + find_vacant_remote_name(new_name, remote->path); + + string_t new_path; + string_init_set(new_path, remote->path); + if(string_end_with_str_p(new_path, InfraredApp::infrared_extension)) { + size_t filename_start = string_search_rchar(new_path, '/'); + string_left(new_path, filename_start); } + string_cat_printf( + new_path, "/%s%s", string_get_cstr(new_name), InfraredApp::infrared_extension); Storage* storage = static_cast(furi_record_open("storage")); - std::string old_filename = make_full_name(remote->path, remote->name); - std::string new_filename = make_full_name(remote->path, new_name); - FS_Error error = storage_common_rename(storage, old_filename.c_str(), new_filename.c_str()); - remote->name = new_name; + FS_Error error = + storage_common_rename(storage, string_get_cstr(remote->path), string_get_cstr(new_path)); + remote->name = std::string(string_get_cstr(new_name)); + + string_clear(new_name); + string_clear(new_path); furi_record_close("storage"); return (error == FSE_OK || error == FSE_EXIST); @@ -171,10 +208,8 @@ bool InfraredAppRemoteManager::store(void) { FlipperFormat* ff = flipper_format_file_alloc(storage); - FURI_LOG_I( - "RemoteManager", "store file: \'%s\'", make_full_name(remote->path, remote->name).c_str()); - result = - flipper_format_file_open_always(ff, make_full_name(remote->path, remote->name).c_str()); + FURI_LOG_I("RemoteManager", "store file: \'%s\'", string_get_cstr(remote->path)); + result = flipper_format_file_open_always(ff, string_get_cstr(remote->path)); if(result) { result = flipper_format_write_header_cstr(ff, "IR signals file", 1); } @@ -192,13 +227,13 @@ bool InfraredAppRemoteManager::store(void) { return result; } -bool InfraredAppRemoteManager::load(const std::string& path, const std::string& remote_name) { +bool InfraredAppRemoteManager::load(string_t path) { bool result = false; Storage* storage = static_cast(furi_record_open("storage")); FlipperFormat* ff = flipper_format_file_alloc(storage); - FURI_LOG_I("RemoteManager", "load file: \'%s\'", make_full_name(path, remote_name).c_str()); - result = flipper_format_file_open_existing(ff, make_full_name(path, remote_name).c_str()); + FURI_LOG_I("RemoteManager", "load file: \'%s\'", string_get_cstr(path)); + result = flipper_format_file_open_existing(ff, string_get_cstr(path)); if(result) { string_t header; string_init(header); @@ -210,7 +245,14 @@ bool InfraredAppRemoteManager::load(const std::string& path, const std::string& string_clear(header); } if(result) { - remote = std::make_unique(path, remote_name); + string_t new_name; + string_init(new_name); + + remote = std::make_unique(path); + path_extract_filename(path, new_name, true); + remote->name = std::string(string_get_cstr(new_name)); + + string_clear(new_name); InfraredAppSignal signal; std::string signal_name; while(infrared_parser_read_signal(ff, signal, signal_name)) { diff --git a/applications/infrared/infrared_app_remote_manager.h b/applications/infrared/infrared_app_remote_manager.h index 31557d54..b6f0b170 100644 --- a/applications/infrared/infrared_app_remote_manager.h +++ b/applications/infrared/infrared_app_remote_manager.h @@ -8,6 +8,7 @@ #include "infrared_app_signal.h" +#include "m-string.h" #include #include @@ -60,17 +61,19 @@ class InfraredAppRemote { /** Name of remote */ std::string name; /** Path to remote file */ - std::string path; + string_t path; public: /** Initialize new remote * * @param path - remote file path - * @param name - new remote name */ - InfraredAppRemote(const std::string& path, const std::string& name) - : name(name) - , path(path) { + InfraredAppRemote(string_t file_path) { + string_init_set(path, file_path); + } + + ~InfraredAppRemote() { + string_clear(path); } }; @@ -78,12 +81,6 @@ public: class InfraredAppRemoteManager { /** Remote instance. There can be 1 remote loaded at a time. */ std::unique_ptr remote; - /** Make full name from remote name - * - * @param remote_name name of remote - * @retval full name of remote on disk - */ - std::string make_full_name(const std::string& path, const std::string& remote_name) const; public: /** Restriction to button name length. Buttons larger are ignored. */ @@ -125,9 +122,9 @@ public: * incremented digit(2,3,4,etc) added to name and check repeated. * * @param name - suggested remote name - * @retval garanteed free remote name, prefixed with suggested + * @param path - remote file path */ - std::string find_vacant_remote_name(const std::string& name); + void find_vacant_remote_name(string_t name, string_t path); /** Get button list * @@ -185,8 +182,8 @@ public: /** Load data from disk into current remote * - * @param name - name of remote to load + * @param path - path to remote file * @retval true if success, false otherwise */ - bool load(const std::string& path, const std::string& name); + bool load(string_t path); }; diff --git a/applications/infrared/scene/infrared_app_scene_edit_rename.cpp b/applications/infrared/scene/infrared_app_scene_edit_rename.cpp index dc63c64b..761da49f 100644 --- a/applications/infrared/scene/infrared_app_scene_edit_rename.cpp +++ b/applications/infrared/scene/infrared_app_scene_edit_rename.cpp @@ -1,4 +1,6 @@ #include "../infrared_app.h" +#include "m-string.h" +#include "toolbox/path.h" void InfraredAppSceneEditRename::on_enter(InfraredApp* app) { InfraredAppViewManager* view_manager = app->get_view_manager(); @@ -21,9 +23,18 @@ void InfraredAppSceneEditRename::on_enter(InfraredApp* app) { enter_name_length = InfraredAppRemoteManager::max_remote_name_length; text_input_set_header_text(text_input, "Name the remote"); + string_t folder_path; + string_init(folder_path); + + if(string_end_with_str_p(app->file_path, InfraredApp::infrared_extension)) { + path_extract_dirname(string_get_cstr(app->file_path), folder_path); + } + ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( - app->infrared_directory, app->infrared_extension, remote_name.c_str()); + string_get_cstr(folder_path), app->infrared_extension, remote_name.c_str()); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); + + string_clear(folder_path); } text_input_set_result_callback( diff --git a/applications/infrared/scene/infrared_app_scene_remote_list.cpp b/applications/infrared/scene/infrared_app_scene_remote_list.cpp index f59ff3e9..c72acb6e 100644 --- a/applications/infrared/scene/infrared_app_scene_remote_list.cpp +++ b/applications/infrared/scene/infrared_app_scene_remote_list.cpp @@ -1,4 +1,5 @@ #include "../infrared_app.h" +#include "assets_icons.h" #include "infrared/infrared_app_event.h" #include @@ -8,11 +9,6 @@ void InfraredAppSceneRemoteList::on_enter(InfraredApp* app) { bool result = false; bool file_select_result; auto remote_manager = app->get_remote_manager(); - auto last_selected_remote = remote_manager->get_remote_name(); - const char* last_selected_remote_name = - last_selected_remote.size() ? last_selected_remote.c_str() : nullptr; - auto filename_ts = - std::make_unique(InfraredAppRemoteManager::max_remote_name_length); DialogsApp* dialogs = app->get_dialogs(); InfraredAppViewManager* view_manager = app->get_view_manager(); @@ -20,16 +16,17 @@ void InfraredAppSceneRemoteList::on_enter(InfraredApp* app) { button_menu_reset(button_menu); view_manager->switch_to(InfraredAppViewManager::ViewId::ButtonMenu); - file_select_result = dialog_file_select_show( + file_select_result = dialog_file_browser_show( dialogs, - InfraredApp::infrared_directory, + app->file_path, + app->file_path, InfraredApp::infrared_extension, - filename_ts->text, - filename_ts->text_size, - last_selected_remote_name); + true, + &I_ir_10px, + true); if(file_select_result) { - if(remote_manager->load(InfraredApp::infrared_directory, std::string(filename_ts->text))) { + if(remote_manager->load(app->file_path)) { app->switch_to_next_scene(InfraredApp::Scene::Remote); result = true; } diff --git a/applications/infrared/scene/infrared_app_scene_start.cpp b/applications/infrared/scene/infrared_app_scene_start.cpp index c42ab9fe..5efdce7a 100644 --- a/applications/infrared/scene/infrared_app_scene_start.cpp +++ b/applications/infrared/scene/infrared_app_scene_start.cpp @@ -26,6 +26,8 @@ void InfraredAppSceneStart::on_enter(InfraredApp* app) { submenu, "Learn New Remote", SubmenuIndexLearnNewRemote, submenu_callback, app); submenu_add_item(submenu, "Saved Remotes", SubmenuIndexSavedRemotes, submenu_callback, app); submenu_set_selected_item(submenu, submenu_item_selected); + + string_set_str(app->file_path, InfraredApp::infrared_directory); submenu_item_selected = 0; view_manager->switch_to(InfraredAppViewManager::ViewId::Submenu); diff --git a/applications/infrared/view/infrared_progress_view.c b/applications/infrared/view/infrared_progress_view.c index c9075147..cd2a0754 100644 --- a/applications/infrared/view/infrared_progress_view.c +++ b/applications/infrared/view/infrared_progress_view.c @@ -59,7 +59,7 @@ static void infrared_progress_view_draw_callback(Canvas* canvas, void* _model) { elements_multiline_text_aligned( canvas, x + 33, y + 37, AlignCenter, AlignCenter, percents_string); - canvas_draw_icon(canvas, x + 11, y + height - 15, &I_Back_15x10); + canvas_draw_icon(canvas, x + 14, y + height - 14, &I_Pin_back_arrow_10x8); canvas_draw_str(canvas, x + 30, y + height - 6, "= stop"); } diff --git a/applications/lfrfid/helpers/decoder_ioprox.cpp b/applications/lfrfid/helpers/decoder_ioprox.cpp new file mode 100644 index 00000000..7b44d3ce --- /dev/null +++ b/applications/lfrfid/helpers/decoder_ioprox.cpp @@ -0,0 +1,107 @@ +#include "decoder_ioprox.h" +#include +#include +#include + +constexpr uint32_t clocks_in_us = 64; + +constexpr uint32_t jitter_time_us = 20; +constexpr uint32_t min_time_us = 64; +constexpr uint32_t max_time_us = 80; +constexpr uint32_t baud_time_us = 500; + +constexpr uint32_t min_time = (min_time_us - jitter_time_us) * clocks_in_us; +constexpr uint32_t mid_time = ((max_time_us - min_time_us) / 2 + min_time_us) * clocks_in_us; +constexpr uint32_t max_time = (max_time_us + jitter_time_us) * clocks_in_us; +constexpr uint32_t baud_time = baud_time_us * clocks_in_us; + +bool DecoderIoProx::read(uint8_t* data, uint8_t data_size) { + bool result = false; + furi_assert(data_size >= 4); + + if(ready) { + result = true; + ioprox.decode(raw_data, sizeof(raw_data), data, data_size); + ready = false; + } + + return result; +} + +void DecoderIoProx::process_front(bool is_rising_edge, uint32_t time) { + if(ready) { + return; + } + + // Always track the time that's gone by. + current_period_duration += time; + demodulation_sample_duration += time; + + // If a baud time has elapsed, we're at a sample point. + if(demodulation_sample_duration >= baud_time) { + // Start a new baud period... + demodulation_sample_duration = 0; + demodulated_value_invalid = false; + + // ... and if we didn't have any baud errors, capture a sample. + if(!demodulated_value_invalid) { + store_data(current_demodulated_value); + } + } + + // + // FSK demodulator. + // + + // If this isn't a rising edge, this isn't a pulse of interest. + // We're done. + if(!is_rising_edge) { + return; + } + + bool is_valid_low = (current_period_duration > min_time) && + (current_period_duration <= mid_time); + bool is_valid_high = (current_period_duration > mid_time) && + (current_period_duration < max_time); + + // If this is between the minimum and our threshold, this is a logical 0. + if(is_valid_low) { + current_demodulated_value = false; + } + // Otherwise, if between our threshold and the max time, it's a logical 1. + else if(is_valid_high) { + current_demodulated_value = true; + } + // Otherwise, invalidate this sample. + else { + demodulated_value_invalid = true; + } + + // We're starting a new period; track that. + current_period_duration = 0; +} + +DecoderIoProx::DecoderIoProx() { + reset_state(); +} + +void DecoderIoProx::store_data(bool data) { + for(int i = 0; i < 7; ++i) { + raw_data[i] = (raw_data[i] << 1) | ((raw_data[i + 1] >> 7) & 1); + } + raw_data[7] = (raw_data[7] << 1) | data; + + if(ioprox.can_be_decoded(raw_data, sizeof(raw_data))) { + ready = true; + } +} + +void DecoderIoProx::reset_state() { + current_demodulated_value = false; + demodulated_value_invalid = false; + + current_period_duration = 0; + demodulation_sample_duration = 0; + + ready = false; +} diff --git a/applications/lfrfid/helpers/decoder_ioprox.h b/applications/lfrfid/helpers/decoder_ioprox.h new file mode 100644 index 00000000..aff4a477 --- /dev/null +++ b/applications/lfrfid/helpers/decoder_ioprox.h @@ -0,0 +1,26 @@ +#pragma once +#include +#include +#include "protocols/protocol_ioprox.h" + +class DecoderIoProx { +public: + bool read(uint8_t* data, uint8_t data_size); + void process_front(bool polarity, uint32_t time); + DecoderIoProx(); + +private: + uint32_t current_period_duration = 0; + uint32_t demodulation_sample_duration = 0; + + bool current_demodulated_value = false; + bool demodulated_value_invalid = false; + + uint8_t raw_data[8] = {0}; + void store_data(bool data); + + std::atomic ready; + + void reset_state(); + ProtocolIoProx ioprox; +}; diff --git a/applications/lfrfid/helpers/encoder_ioprox.cpp b/applications/lfrfid/helpers/encoder_ioprox.cpp new file mode 100644 index 00000000..41779918 --- /dev/null +++ b/applications/lfrfid/helpers/encoder_ioprox.cpp @@ -0,0 +1,32 @@ +#include "encoder_ioprox.h" +#include "protocols/protocol_ioprox.h" +#include + +void EncoderIoProx::init(const uint8_t* data, const uint8_t data_size) { + ProtocolIoProx ioprox; + ioprox.encode(data, data_size, card_data, sizeof(card_data)); + card_data_index = 0; +} + +void EncoderIoProx::get_next(bool* polarity, uint16_t* period, uint16_t* pulse) { + uint8_t bit = (card_data[card_data_index / 8] >> (7 - (card_data_index % 8))) & 1; + + bool advance = fsk->next(bit, period); + if(advance) { + card_data_index++; + if(card_data_index >= (8 * card_data_max)) { + card_data_index = 0; + } + } + + *polarity = true; + *pulse = *period / 2; +} + +EncoderIoProx::EncoderIoProx() { + fsk = new OscFSK(8, 10, 64); +} + +EncoderIoProx::~EncoderIoProx() { + delete fsk; +} diff --git a/applications/lfrfid/helpers/encoder_ioprox.h b/applications/lfrfid/helpers/encoder_ioprox.h new file mode 100644 index 00000000..568b4067 --- /dev/null +++ b/applications/lfrfid/helpers/encoder_ioprox.h @@ -0,0 +1,25 @@ +#pragma once +#include "encoder_generic.h" +#include "osc_fsk.h" + +class EncoderIoProx : public EncoderGeneric { +public: + /** + * @brief init data to emulate + * + * @param data 1 byte FC, 1 byte Version, 2 bytes code + * @param data_size must be 4 + */ + void init(const uint8_t* data, const uint8_t data_size) final; + void get_next(bool* polarity, uint16_t* period, uint16_t* pulse) final; + EncoderIoProx(); + ~EncoderIoProx(); + +private: + static const uint8_t card_data_max = 8; + + uint8_t card_data[card_data_max]; + uint8_t card_data_index; + + OscFSK* fsk; +}; diff --git a/applications/lfrfid/helpers/key_info.cpp b/applications/lfrfid/helpers/key_info.cpp index ed2b20a9..4803cd6d 100644 --- a/applications/lfrfid/helpers/key_info.cpp +++ b/applications/lfrfid/helpers/key_info.cpp @@ -12,6 +12,9 @@ const char* lfrfid_key_get_type_string(LfrfidKeyType type) { case LfrfidKeyType::KeyI40134: return "I40134"; break; + case LfrfidKeyType::KeyIoProxXSF: + return "IoProxXSF"; + break; } return "Unknown"; @@ -28,6 +31,8 @@ const char* lfrfid_key_get_manufacturer_string(LfrfidKeyType type) { case LfrfidKeyType::KeyI40134: return "Indala"; break; + case LfrfidKeyType::KeyIoProxXSF: + return "Kantech"; } return "Unknown"; @@ -42,6 +47,8 @@ bool lfrfid_key_get_string_type(const char* string, LfrfidKeyType* type) { *type = LfrfidKeyType::KeyH10301; } else if(strcmp("I40134", string) == 0) { *type = LfrfidKeyType::KeyI40134; + } else if(strcmp("IoProxXSF", string) == 0) { + *type = LfrfidKeyType::KeyIoProxXSF; } else { result = false; } @@ -60,6 +67,9 @@ uint8_t lfrfid_key_get_type_data_count(LfrfidKeyType type) { case LfrfidKeyType::KeyI40134: return 3; break; + case LfrfidKeyType::KeyIoProxXSF: + return 4; + break; } return 0; diff --git a/applications/lfrfid/helpers/key_info.h b/applications/lfrfid/helpers/key_info.h index e465011d..75a0a940 100644 --- a/applications/lfrfid/helpers/key_info.h +++ b/applications/lfrfid/helpers/key_info.h @@ -8,6 +8,7 @@ enum class LfrfidKeyType : uint8_t { KeyEM4100, KeyH10301, KeyI40134, + KeyIoProxXSF, }; const char* lfrfid_key_get_type_string(LfrfidKeyType type); diff --git a/applications/lfrfid/helpers/protocols/protocol_ioprox.cpp b/applications/lfrfid/helpers/protocols/protocol_ioprox.cpp new file mode 100644 index 00000000..b3b6a0e5 --- /dev/null +++ b/applications/lfrfid/helpers/protocols/protocol_ioprox.cpp @@ -0,0 +1,193 @@ +#include "protocol_ioprox.h" +#include +#include + +/** + * Writes a bit into the output buffer. + */ +static void write_bit(bool bit, uint8_t position, uint8_t* data) { + if(bit) { + data[position / 8] |= 1UL << (7 - (position % 8)); + } else { + data[position / 8] &= ~(1UL << (7 - (position % 8))); + } +} + +/** + * Writes up to eight contiguous bits into the output buffer. + */ +static void write_bits(uint8_t byte, uint8_t position, uint8_t* data, uint8_t length) { + furi_check(length <= 8); + furi_check(length > 0); + + for(uint8_t i = 0; i < length; ++i) { + uint8_t shift = 7 - i; + write_bit((byte >> shift) & 1, position + i, data); + } +} + +uint8_t ProtocolIoProx::get_encoded_data_size() { + return 8; +} + +uint8_t ProtocolIoProx::get_decoded_data_size() { + return 4; +} + +void ProtocolIoProx::encode( + const uint8_t* decoded_data, + const uint8_t decoded_data_size, + uint8_t* encoded_data, + const uint8_t encoded_data_size) { + furi_check(decoded_data_size >= get_decoded_data_size()); + furi_check(encoded_data_size >= get_encoded_data_size()); + + // Packet to transmit: + // + // 0 10 20 30 40 50 60 + // v v v v v v v + // 01234567 8 90123456 7 89012345 6 78901234 5 67890123 4 56789012 3 45678901 23 + // ----------------------------------------------------------------------------- + // 00000000 0 11110000 1 facility 1 version_ 1 code-one 1 code-two 1 checksum 11 + + // Preamble. + write_bits(0b00000000, 0, encoded_data, 8); + write_bit(0, 8, encoded_data); + + write_bits(0b11110000, 9, encoded_data, 8); + write_bit(1, 17, encoded_data); + + // Facility code. + write_bits(decoded_data[0], 18, encoded_data, 8); + write_bit(1, 26, encoded_data); + + // Version + write_bits(decoded_data[1], 27, encoded_data, 8); + write_bit(1, 35, encoded_data); + + // Code one + write_bits(decoded_data[2], 36, encoded_data, 8); + write_bit(1, 44, encoded_data); + + // Code two + write_bits(decoded_data[3], 45, encoded_data, 8); + write_bit(1, 53, encoded_data); + + // Checksum + write_bits(compute_checksum(encoded_data, 8), 54, encoded_data, 8); + write_bit(1, 62, encoded_data); + write_bit(1, 63, encoded_data); +} + +void ProtocolIoProx::decode( + const uint8_t* encoded_data, + const uint8_t encoded_data_size, + uint8_t* decoded_data, + const uint8_t decoded_data_size) { + furi_check(decoded_data_size >= get_decoded_data_size()); + furi_check(encoded_data_size >= get_encoded_data_size()); + + // Packet structure: + // (Note: the second word seems fixed; but this may not be a guarantee; + // it currently has no meaning.) + // + //0 1 2 3 4 5 6 7 + //v v v v v v v v + //01234567 89ABCDEF 01234567 89ABCDEF 01234567 89ABCDEF 01234567 89ABCDEF + //----------------------------------------------------------------------- + //00000000 01111000 01FFFFFF FF1VVVVV VVV1CCCC CCCC1CCC CCCCC1XX XXXXXX11 + // + // F = facility code + // V = version + // C = code + // X = checksum + + // Facility code + decoded_data[0] = (encoded_data[2] << 2) | (encoded_data[3] >> 6); + + // Version code. + decoded_data[1] = (encoded_data[3] << 3) | (encoded_data[4] >> 5); + + // Code bytes. + decoded_data[2] = (encoded_data[4] << 4) | (encoded_data[5] >> 4); + decoded_data[3] = (encoded_data[5] << 5) | (encoded_data[6] >> 3); +} + +bool ProtocolIoProx::can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) { + furi_check(encoded_data_size >= get_encoded_data_size()); + + // Packet framing + // + //0 1 2 3 4 5 6 7 + //v v v v v v v v + //01234567 89ABCDEF 01234567 89ABCDEF 01234567 89ABCDEF 01234567 89ABCDEF + //----------------------------------------------------------------------- + //00000000 01______ _1______ __1_____ ___1____ ____1___ _____1XX XXXXXX11 + // + // _ = variable data + // 0 = preamble 0 + // 1 = framing 1 + // X = checksum + + // Validate the packet preamble is there... + if(encoded_data[0] != 0b00000000) { + return false; + } + if((encoded_data[1] >> 6) != 0b01) { + return false; + } + + // ... check for known ones... + if((encoded_data[2] & 0b01000000) == 0) { + return false; + } + if((encoded_data[3] & 0b00100000) == 0) { + return false; + } + if((encoded_data[4] & 0b00010000) == 0) { + return false; + } + if((encoded_data[5] & 0b00001000) == 0) { + return false; + } + if((encoded_data[6] & 0b00000100) == 0) { + return false; + } + if((encoded_data[7] & 0b00000011) == 0) { + return false; + } + + // ... and validate our checksums. + uint8_t checksum = compute_checksum(encoded_data, 8); + uint8_t checkval = (encoded_data[6] << 6) | (encoded_data[7] >> 2); + + if(checksum != checkval) { + return false; + } + + return true; +} + +uint8_t ProtocolIoProx::compute_checksum(const uint8_t* data, const uint8_t data_size) { + furi_check(data_size == get_encoded_data_size()); + + // Packet structure: + // + //0 1 2 3 4 5 6 7 + //v v v v v v v v + //01234567 8 9ABCDEF0 1 23456789 A BCDEF012 3 456789AB C DEF01234 5 6789ABCD EF + //00000000 0 VVVVVVVV 1 WWWWWWWW 1 XXXXXXXX 1 YYYYYYYY 1 ZZZZZZZZ 1 CHECKSUM 11 + // + // algorithm as observed by the proxmark3 folks + // CHECKSUM == 0xFF - (V + W + X + Y + Z) + + uint8_t checksum = 0; + + checksum += (data[1] << 1) | (data[2] >> 7); // VVVVVVVVV + checksum += (data[2] << 2) | (data[3] >> 6); // WWWWWWWWW + checksum += (data[3] << 3) | (data[4] >> 5); // XXXXXXXXX + checksum += (data[4] << 4) | (data[5] >> 4); // YYYYYYYYY + checksum += (data[5] << 5) | (data[6] >> 3); // ZZZZZZZZZ + + return 0xFF - checksum; +} diff --git a/applications/lfrfid/helpers/protocols/protocol_ioprox.h b/applications/lfrfid/helpers/protocols/protocol_ioprox.h new file mode 100644 index 00000000..2fff53b1 --- /dev/null +++ b/applications/lfrfid/helpers/protocols/protocol_ioprox.h @@ -0,0 +1,26 @@ +#pragma once +#include "protocol_generic.h" + +class ProtocolIoProx : public ProtocolGeneric { +public: + uint8_t get_encoded_data_size() final; + uint8_t get_decoded_data_size() final; + + void encode( + const uint8_t* decoded_data, + const uint8_t decoded_data_size, + uint8_t* encoded_data, + const uint8_t encoded_data_size) final; + + void decode( + const uint8_t* encoded_data, + const uint8_t encoded_data_size, + uint8_t* decoded_data, + const uint8_t decoded_data_size) final; + + bool can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) final; + +private: + /** Computes the IoProx checksum of the provided (decoded) data. */ + uint8_t compute_checksum(const uint8_t* data, const uint8_t data_size); +}; diff --git a/applications/lfrfid/helpers/rfid_reader.cpp b/applications/lfrfid/helpers/rfid_reader.cpp index 1a6ed5f7..fb837cb7 100644 --- a/applications/lfrfid/helpers/rfid_reader.cpp +++ b/applications/lfrfid/helpers/rfid_reader.cpp @@ -25,10 +25,12 @@ void RfidReader::decode(bool polarity) { case Type::Normal: decoder_em.process_front(polarity, period); decoder_hid26.process_front(polarity, period); + decoder_ioprox.process_front(polarity, period); break; case Type::Indala: decoder_em.process_front(polarity, period); decoder_hid26.process_front(polarity, period); + decoder_ioprox.process_front(polarity, period); decoder_indala.process_front(polarity, period); break; } @@ -110,6 +112,11 @@ bool RfidReader::read(LfrfidKeyType* _type, uint8_t* data, uint8_t data_size, bo something_read = true; } + if(decoder_ioprox.read(data, data_size)) { + *_type = LfrfidKeyType::KeyIoProxXSF; + something_read = true; + } + if(decoder_indala.read(data, data_size)) { *_type = LfrfidKeyType::KeyI40134; something_read = true; diff --git a/applications/lfrfid/helpers/rfid_reader.h b/applications/lfrfid/helpers/rfid_reader.h index b3a93b89..903bbecf 100644 --- a/applications/lfrfid/helpers/rfid_reader.h +++ b/applications/lfrfid/helpers/rfid_reader.h @@ -4,6 +4,7 @@ #include "decoder_emmarin.h" #include "decoder_hid26.h" #include "decoder_indala.h" +#include "decoder_ioprox.h" #include "key_info.h" //#define RFID_GPIO_DEBUG 1 @@ -34,6 +35,7 @@ private: DecoderEMMarin decoder_em; DecoderHID26 decoder_hid26; DecoderIndala decoder_indala; + DecoderIoProx decoder_ioprox; uint32_t last_dwt_value; diff --git a/applications/lfrfid/helpers/rfid_timer_emulator.h b/applications/lfrfid/helpers/rfid_timer_emulator.h index 874a4c3d..2129a1c7 100644 --- a/applications/lfrfid/helpers/rfid_timer_emulator.h +++ b/applications/lfrfid/helpers/rfid_timer_emulator.h @@ -5,6 +5,7 @@ #include "encoder_emmarin.h" #include "encoder_hid_h10301.h" #include "encoder_indala_40134.h" +#include "encoder_ioprox.h" #include "pulse_joiner.h" #include @@ -22,6 +23,7 @@ private: {LfrfidKeyType::KeyEM4100, new EncoderEM()}, {LfrfidKeyType::KeyH10301, new EncoderHID_H10301()}, {LfrfidKeyType::KeyI40134, new EncoderIndala_40134()}, + {LfrfidKeyType::KeyIoProxXSF, new EncoderIoProx()}, }; PulseJoiner pulse_joiner; diff --git a/applications/lfrfid/helpers/rfid_worker.cpp b/applications/lfrfid/helpers/rfid_worker.cpp index 6e023cfb..af15a340 100644 --- a/applications/lfrfid/helpers/rfid_worker.cpp +++ b/applications/lfrfid/helpers/rfid_worker.cpp @@ -84,6 +84,11 @@ void RfidWorker::sq_write() { writer.write_indala(key.get_data()); writer.stop(); break; + case LfrfidKeyType::KeyIoProxXSF: + writer.start(); + writer.write_ioprox(key.get_data()); + writer.stop(); + break; } } } @@ -92,6 +97,7 @@ void RfidWorker::sq_write_start_validate() { switch(key.get_type()) { case LfrfidKeyType::KeyEM4100: case LfrfidKeyType::KeyH10301: + case LfrfidKeyType::KeyIoProxXSF: reader.start_forced(RfidReader::Type::Normal); break; case LfrfidKeyType::KeyI40134: diff --git a/applications/lfrfid/helpers/rfid_writer.cpp b/applications/lfrfid/helpers/rfid_writer.cpp index 996cb36a..e85ab936 100644 --- a/applications/lfrfid/helpers/rfid_writer.cpp +++ b/applications/lfrfid/helpers/rfid_writer.cpp @@ -1,4 +1,5 @@ #include "rfid_writer.h" +#include "protocols/protocol_ioprox.h" #include #include "protocols/protocol_emmarin.h" #include "protocols/protocol_hid_h10301.h" @@ -143,6 +144,28 @@ void RfidWriter::write_hid(const uint8_t hid_data[3]) { FURI_CRITICAL_EXIT(); } +/** Endian fixup. Translates an ioprox block into a t5577 block */ +static uint32_t ioprox_encode_block(const uint8_t block_data[4]) { + uint8_t raw_card_data[] = {block_data[3], block_data[2], block_data[1], block_data[0]}; + return *reinterpret_cast(&raw_card_data); +} + +void RfidWriter::write_ioprox(const uint8_t ioprox_data[4]) { + ProtocolIoProx ioprox_card; + + uint8_t encoded_data[8]; + ioprox_card.encode(ioprox_data, 4, encoded_data, sizeof(encoded_data)); + + const uint32_t ioprox_config_block_data = 0b00000000000101000111000001000000; + + FURI_CRITICAL_ENTER(); + write_block(0, 0, false, ioprox_config_block_data); + write_block(0, 1, false, ioprox_encode_block(&encoded_data[0])); + write_block(0, 2, false, ioprox_encode_block(&encoded_data[4])); + write_reset(); + FURI_CRITICAL_EXIT(); +} + void RfidWriter::write_indala(const uint8_t indala_data[3]) { ProtocolIndala40134 indala_card; uint32_t card_data[2]; diff --git a/applications/lfrfid/helpers/rfid_writer.h b/applications/lfrfid/helpers/rfid_writer.h index 38329877..98d2bf95 100644 --- a/applications/lfrfid/helpers/rfid_writer.h +++ b/applications/lfrfid/helpers/rfid_writer.h @@ -9,6 +9,7 @@ public: void stop(); void write_em(const uint8_t em_data[5]); void write_hid(const uint8_t hid_data[3]); + void write_ioprox(const uint8_t ioprox_data[4]); void write_indala(const uint8_t indala_data[3]); private: diff --git a/applications/lfrfid/lfrfid_app.cpp b/applications/lfrfid/lfrfid_app.cpp index c2274234..4027a07e 100644 --- a/applications/lfrfid/lfrfid_app.cpp +++ b/applications/lfrfid/lfrfid_app.cpp @@ -1,4 +1,7 @@ #include "lfrfid_app.h" +#include "assets_icons.h" +#include "furi/common_defines.h" +#include "m-string.h" #include "scene/lfrfid_app_scene_start.h" #include "scene/lfrfid_app_scene_read.h" #include "scene/lfrfid_app_scene_read_success.h" @@ -31,9 +34,11 @@ LfRfidApp::LfRfidApp() , storage{"storage"} , dialogs{"dialogs"} , text_store(40) { + string_init_set_str(file_path, app_folder); } LfRfidApp::~LfRfidApp() { + string_clear(file_path); } void LfRfidApp::run(void* _args) { @@ -42,7 +47,8 @@ void LfRfidApp::run(void* _args) { make_app_folder(); if(strlen(args)) { - load_key_data(args, &worker.key); + string_set_str(file_path, args); + load_key_data(file_path, &worker.key); scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate()); scene_controller.process(100, SceneType::Emulate); } else { @@ -69,65 +75,49 @@ void LfRfidApp::run(void* _args) { } 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); + if(string_end_with_str_p(file_path, app_extension)) { + size_t filename_start = string_search_rchar(file_path, '/'); + string_left(file_path, filename_start); + } + string_cat_printf(file_path, "/%s%s", key->get_name(), app_extension); + + result = save_key_data(file_path, key); return result; } bool LfRfidApp::load_key_from_file_select(bool need_restore) { - TextStore* filename_ts = new TextStore(64); - bool result = false; - - if(need_restore) { - result = dialog_file_select_show( - dialogs, - app_folder, - app_extension, - filename_ts->text, - filename_ts->text_size, - worker.key.get_name()); - } else { - result = dialog_file_select_show( - dialogs, app_folder, app_extension, filename_ts->text, filename_ts->text_size, NULL); + if(!need_restore) { + string_set_str(file_path, app_folder); } + bool result = dialog_file_browser_show( + dialogs, file_path, file_path, app_extension, true, &I_125_10px, true); + 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); + result = load_key_data(file_path, &worker.key); } - delete filename_ts; return result; } bool LfRfidApp::delete_key(RfidKey* key) { - string_t file_name; - bool result = false; - - string_init_printf(file_name, "%s/%s%s", app_folder, key->get_name(), app_extension); - result = storage_simply_remove(storage, string_get_cstr(file_name)); - string_clear(file_name); - - return result; + UNUSED(key); + return storage_simply_remove(storage, string_get_cstr(file_path)); } -bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { +bool LfRfidApp::load_key_data(string_t path, RfidKey* key) { FlipperFormat* file = flipper_format_file_alloc(storage); bool result = false; string_t str_result; string_init(str_result); do { - if(!flipper_format_file_open_existing(file, path)) break; + if(!flipper_format_file_open_existing(file, string_get_cstr(path))) break; // header uint32_t version; @@ -149,7 +139,7 @@ bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { break; loaded_key.set_data(key_data, loaded_key.get_type_data_count()); - path_extract_filename_no_ext(path, str_result); + path_extract_filename(path, str_result, true); loaded_key.set_name(string_get_cstr(str_result)); *key = loaded_key; @@ -166,12 +156,12 @@ bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { return result; } -bool LfRfidApp::save_key_data(const char* path, RfidKey* key) { +bool LfRfidApp::save_key_data(string_t path, RfidKey* key) { FlipperFormat* file = flipper_format_file_alloc(storage); bool result = false; do { - if(!flipper_format_file_open_always(file, path)) break; + if(!flipper_format_file_open_always(file, string_get_cstr(path))) break; if(!flipper_format_write_header_cstr(file, app_filetype, 1)) break; if(!flipper_format_write_comment_cstr(file, "Key type can be EM4100, H10301 or I40134")) break; diff --git a/applications/lfrfid/lfrfid_app.h b/applications/lfrfid/lfrfid_app.h index dddfb753..3f8209a1 100644 --- a/applications/lfrfid/lfrfid_app.h +++ b/applications/lfrfid/lfrfid_app.h @@ -1,4 +1,5 @@ #pragma once +#include "m-string.h" #include #include @@ -76,6 +77,8 @@ public: TextStore text_store; + string_t file_path; + void run(void* args); static const char* app_folder; @@ -86,8 +89,8 @@ public: 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); + bool load_key_data(string_t path, RfidKey* key); + bool save_key_data(string_t path, RfidKey* key); void make_app_folder(); }; diff --git a/applications/lfrfid/lfrfid_cli.cpp b/applications/lfrfid/lfrfid_cli.cpp index dbd37ddc..451f10ce 100644 --- a/applications/lfrfid/lfrfid_cli.cpp +++ b/applications/lfrfid/lfrfid_cli.cpp @@ -28,6 +28,7 @@ void lfrfid_cli_print_usage() { printf("\tEM4100, EM-Marin (5 bytes key_data)\r\n"); printf("\tH10301, HID26 (3 bytes key_data)\r\n"); printf("\tI40134, Indala (3 bytes key_data)\r\n"); + printf("\tIoProxXSF, IoProx (4 bytes key_data)\r\n"); printf("\t are hex-formatted\r\n"); }; @@ -43,6 +44,9 @@ static bool lfrfid_cli_get_key_type(string_t data, LfrfidKeyType* type) { } else if(string_cmp_str(data, "I40134") == 0 || string_cmp_str(data, "Indala") == 0) { result = true; *type = LfrfidKeyType::KeyI40134; + } else if(string_cmp_str(data, "IoProxXSF") == 0 || string_cmp_str(data, "IoProx") == 0) { + result = true; + *type = LfrfidKeyType::KeyIoProxXSF; } return result; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_delete_confirm.cpp b/applications/lfrfid/scene/lfrfid_app_scene_delete_confirm.cpp index 40bd9e36..236ca8c2 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_delete_confirm.cpp +++ b/applications/lfrfid/scene/lfrfid_app_scene_delete_confirm.cpp @@ -50,6 +50,14 @@ void LfRfidAppSceneDeleteConfirm::on_enter(LfRfidApp* app, bool /* need_restore string_printf( string_decrypted, "FC: %u ID: %u", data[0], (uint16_t)((data[1] << 8) | (data[2]))); break; + case LfrfidKeyType::KeyIoProxXSF: + string_printf( + string_decrypted, + "FC: %u VC: %u ID: %u", + data[0], + data[1], + (uint16_t)((data[2] << 8) | (data[3]))); + break; } line_3->set_text( string_get_cstr(string_decrypted), 64, 39, 0, AlignCenter, AlignBottom, FontSecondary); diff --git a/applications/lfrfid/scene/lfrfid_app_scene_read_success.cpp b/applications/lfrfid/scene/lfrfid_app_scene_read_success.cpp index 2b81a58a..010cac2c 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_read_success.cpp +++ b/applications/lfrfid/scene/lfrfid_app_scene_read_success.cpp @@ -7,6 +7,7 @@ void LfRfidAppSceneReadSuccess::on_enter(LfRfidApp* app, bool /* need_restore */ string_init(string[0]); string_init(string[1]); string_init(string[2]); + string_init(string[3]); auto container = app->view_controller.get(); @@ -25,11 +26,13 @@ void LfRfidAppSceneReadSuccess::on_enter(LfRfidApp* app, bool /* need_restore */ header->set_text(app->worker.key.get_type_text(), 89, 3, 0, AlignCenter); auto line_1_text = container->add(); - auto line_2_text = container->add(); + auto line_2l_text = container->add(); + auto line_2r_text = container->add(); auto line_3_text = container->add(); auto line_1_value = container->add(); - auto line_2_value = container->add(); + auto line_2l_value = container->add(); + auto line_2r_value = container->add(); auto line_3_value = container->add(); const uint8_t* data = app->worker.key.get_data(); @@ -37,7 +40,7 @@ void LfRfidAppSceneReadSuccess::on_enter(LfRfidApp* app, bool /* need_restore */ switch(app->worker.key.get_type()) { case LfrfidKeyType::KeyEM4100: line_1_text->set_text("HEX:", 65, 23, 0, AlignRight, AlignBottom, FontSecondary); - line_2_text->set_text("Mod:", 65, 35, 0, AlignRight, AlignBottom, FontSecondary); + line_2l_text->set_text("Mod:", 65, 35, 0, AlignRight, AlignBottom, FontSecondary); line_3_text->set_text("ID:", 65, 47, 0, AlignRight, AlignBottom, FontSecondary); for(uint8_t i = 0; i < app->worker.key.get_type_data_count(); i++) { @@ -49,7 +52,7 @@ void LfRfidAppSceneReadSuccess::on_enter(LfRfidApp* app, bool /* need_restore */ line_1_value->set_text( string_get_cstr(string[0]), 68, 23, 0, AlignLeft, AlignBottom, FontSecondary); - line_2_value->set_text( + line_2l_value->set_text( string_get_cstr(string[1]), 68, 35, 0, AlignLeft, AlignBottom, FontSecondary); line_3_value->set_text( string_get_cstr(string[2]), 68, 47, 0, AlignLeft, AlignBottom, FontSecondary); @@ -57,7 +60,7 @@ void LfRfidAppSceneReadSuccess::on_enter(LfRfidApp* app, bool /* need_restore */ case LfrfidKeyType::KeyH10301: case LfrfidKeyType::KeyI40134: line_1_text->set_text("HEX:", 65, 23, 0, AlignRight, AlignBottom, FontSecondary); - line_2_text->set_text("FC:", 65, 35, 0, AlignRight, AlignBottom, FontSecondary); + line_2l_text->set_text("FC:", 65, 35, 0, AlignRight, AlignBottom, FontSecondary); line_3_text->set_text("Card:", 65, 47, 0, AlignRight, AlignBottom, FontSecondary); for(uint8_t i = 0; i < app->worker.key.get_type_data_count(); i++) { @@ -69,11 +72,36 @@ void LfRfidAppSceneReadSuccess::on_enter(LfRfidApp* app, bool /* need_restore */ line_1_value->set_text( string_get_cstr(string[0]), 68, 23, 0, AlignLeft, AlignBottom, FontSecondary); - line_2_value->set_text( + line_2l_value->set_text( string_get_cstr(string[1]), 68, 35, 0, AlignLeft, AlignBottom, FontSecondary); line_3_value->set_text( string_get_cstr(string[2]), 68, 47, 0, AlignLeft, AlignBottom, FontSecondary); break; + + case LfrfidKeyType::KeyIoProxXSF: + line_1_text->set_text("HEX:", 65, 23, 0, AlignRight, AlignBottom, FontSecondary); + line_2l_text->set_text("FC:", 65, 35, 0, AlignRight, AlignBottom, FontSecondary); + line_2r_text->set_text("VС:", 95, 35, 0, AlignRight, AlignBottom, FontSecondary); + line_3_text->set_text("Card:", 65, 47, 0, AlignRight, AlignBottom, FontSecondary); + + for(uint8_t i = 0; i < app->worker.key.get_type_data_count(); i++) { + string_cat_printf(string[0], "%02X", data[i]); + } + + string_printf(string[1], "%u", data[0]); + string_printf(string[2], "%u", (uint16_t)((data[2] << 8) | (data[3]))); + string_printf(string[3], "%u", data[1]); + + line_1_value->set_text( + string_get_cstr(string[0]), 68, 23, 0, AlignLeft, AlignBottom, FontSecondary); + line_2l_value->set_text( + string_get_cstr(string[1]), 68, 35, 0, AlignLeft, AlignBottom, FontSecondary); + line_2r_value->set_text( + string_get_cstr(string[3]), 98, 35, 0, AlignLeft, AlignBottom, FontSecondary); + line_3_value->set_text( + string_get_cstr(string[2]), 68, 47, 0, AlignLeft, AlignBottom, FontSecondary); + + break; } app->view_controller.switch_to(); diff --git a/applications/lfrfid/scene/lfrfid_app_scene_save_data.cpp b/applications/lfrfid/scene/lfrfid_app_scene_save_data.cpp index b36ff7e3..3a13e683 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_save_data.cpp +++ b/applications/lfrfid/scene/lfrfid_app_scene_save_data.cpp @@ -1,23 +1,10 @@ #include "lfrfid_app_scene_save_data.h" #include -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(); 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) { diff --git a/applications/lfrfid/scene/lfrfid_app_scene_save_name.cpp b/applications/lfrfid/scene/lfrfid_app_scene_save_name.cpp index d460724e..d7ba2c9e 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_save_name.cpp +++ b/applications/lfrfid/scene/lfrfid_app_scene_save_name.cpp @@ -1,11 +1,14 @@ #include "lfrfid_app_scene_save_name.h" +#include "m-string.h" #include +#include void LfRfidAppSceneSaveName::on_enter(LfRfidApp* app, bool /* need_restore */) { const char* key_name = app->worker.key.get_name(); bool key_name_empty = !strcmp(key_name, ""); if(key_name_empty) { + string_set_str(app->file_path, app->app_folder); set_random_name(app->text_store.text, app->text_store.text_size); } else { app->text_store.set("%s", key_name); @@ -21,10 +24,17 @@ void LfRfidAppSceneSaveName::on_enter(LfRfidApp* app, bool /* need_restore */) { app->worker.key.get_name_length(), key_name_empty); + string_t folder_path; + string_init(folder_path); + + path_extract_dirname(string_get_cstr(app->file_path), folder_path); + ValidatorIsFile* validator_is_file = - validator_is_file_alloc_init(app->app_folder, app->app_extension, key_name); + validator_is_file_alloc_init(string_get_cstr(folder_path), app->app_extension, key_name); text_input->set_validator(validator_is_file_callback, validator_is_file); + string_clear(folder_path); + app->view_controller.switch_to(); } diff --git a/applications/lfrfid/scene/lfrfid_app_scene_save_type.h b/applications/lfrfid/scene/lfrfid_app_scene_save_type.h index 1f6f6d74..847c0dab 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_save_type.h +++ b/applications/lfrfid/scene/lfrfid_app_scene_save_type.h @@ -10,6 +10,6 @@ public: private: static void submenu_callback(void* context, uint32_t index); uint32_t submenu_item_selected = 0; - static const uint8_t keys_count = static_cast(LfrfidKeyType::KeyI40134); + static const uint8_t keys_count = static_cast(LfrfidKeyType::KeyIoProxXSF); string_t submenu_name[keys_count + 1]; }; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_saved_info.cpp b/applications/lfrfid/scene/lfrfid_app_scene_saved_info.cpp index 73c9a403..dd4a3d4e 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_saved_info.cpp +++ b/applications/lfrfid/scene/lfrfid_app_scene_saved_info.cpp @@ -43,6 +43,14 @@ void LfRfidAppSceneSavedInfo::on_enter(LfRfidApp* app, bool /* need_restore */) string_printf( string_decrypted, "FC: %u ID: %u", data[0], (uint16_t)((data[1] << 8) | (data[2]))); break; + case LfrfidKeyType::KeyIoProxXSF: + string_printf( + string_decrypted, + "FC: %u VC: %u ID: %u", + data[0], + data[1], + (uint16_t)((data[2] << 8) | (data[3]))); + break; } line_3->set_text( string_get_cstr(string_decrypted), 64, 39, 0, AlignCenter, AlignBottom, FontSecondary); diff --git a/applications/music_player/music_player.c b/applications/music_player/music_player.c index 6dfef492..9b5dda0f 100644 --- a/applications/music_player/music_player.c +++ b/applications/music_player/music_player.c @@ -1,3 +1,5 @@ +#include "assets_icons.h" +#include "m-string.h" #include #include @@ -298,23 +300,23 @@ int32_t music_player_app(void* p) { if(p) { string_cat_str(file_path, p); } else { - char file_name[256] = {0}; + string_set_str(file_path, MUSIC_PLAYER_APP_PATH_FOLDER); + DialogsApp* dialogs = furi_record_open("dialogs"); - bool res = dialog_file_select_show( + bool res = dialog_file_browser_show( dialogs, - MUSIC_PLAYER_APP_PATH_FOLDER, + file_path, + file_path, MUSIC_PLAYER_APP_EXTENSION, - file_name, - 255, - NULL); + true, + &I_music_10px, + false); + furi_record_close("dialogs"); if(!res) { FURI_LOG_E(TAG, "No file selected"); break; } - string_cat_str(file_path, MUSIC_PLAYER_APP_PATH_FOLDER); - string_cat_str(file_path, "/"); - string_cat_str(file_path, file_name); } if(!music_player_worker_load(music_player->worker, string_get_cstr(file_path))) { diff --git a/applications/music_player/music_player_worker.c b/applications/music_player/music_player_worker.c index 7a5847c9..f43aa7be 100644 --- a/applications/music_player/music_player_worker.c +++ b/applications/music_player/music_player_worker.c @@ -59,9 +59,10 @@ static int32_t music_player_worker_thread_callback(void* context) { float frequency = NOTE_C4 * powf(TWO_POW_TWELTH_ROOT, note_from_a4); float duration = 60.0 * osKernelGetTickFreq() * 4 / instance->bpm / note_block->duration; - while(note_block->dots > 0) { + uint32_t dots = note_block->dots; + while(dots > 0) { duration += duration / 2; - note_block->dots--; + dots--; } uint32_t next_tick = furi_hal_get_tick() + duration; float volume = instance->volume; diff --git a/applications/nfc/nfc.c b/applications/nfc/nfc.c index 4660b344..c4eaf900 100755 --- a/applications/nfc/nfc.c +++ b/applications/nfc/nfc.c @@ -173,6 +173,8 @@ int32_t nfc_app(void* p) { if(nfc_device_load(nfc->dev, p)) { if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareUl); + } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareClassic); } else { scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); } diff --git a/applications/nfc/nfc_device.c b/applications/nfc/nfc_device.c index 9970cbfe..51e14a6c 100644 --- a/applications/nfc/nfc_device.c +++ b/applications/nfc/nfc_device.c @@ -1,4 +1,6 @@ #include "nfc_device.h" +#include "assets_icons.h" +#include "m-string.h" #include "nfc_types.h" #include @@ -7,10 +9,14 @@ static const char* nfc_file_header = "Flipper NFC device"; static const uint32_t nfc_file_version = 2; +// Protocols format versions +static const uint32_t nfc_mifare_classic_data_format_version = 1; + NfcDevice* nfc_device_alloc() { NfcDevice* nfc_dev = malloc(sizeof(NfcDevice)); nfc_dev->storage = furi_record_open("storage"); nfc_dev->dialogs = furi_record_open("dialogs"); + string_init(nfc_dev->load_path); return nfc_dev; } @@ -19,6 +25,7 @@ void nfc_device_free(NfcDevice* nfc_dev) { nfc_device_clear(nfc_dev); furi_record_close("storage"); furi_record_close("dialogs"); + string_clear(nfc_dev->load_path); free(nfc_dev); } @@ -192,6 +199,10 @@ static bool nfc_device_save_mifare_df_key_settings( string_printf(key, "%s Key Changeable", prefix); if(!flipper_format_write_bool(file, string_get_cstr(key), &ks->master_key_changeable, 1)) break; + if(ks->flags) { + string_printf(key, "%s Flags", prefix); + if(!flipper_format_write_hex(file, string_get_cstr(key), &ks->flags, 1)) break; + } string_printf(key, "%s Max Keys", prefix); if(!flipper_format_write_hex(file, string_get_cstr(key), &ks->max_keys, 1)) break; for(MifareDesfireKeyVersion* kv = ks->key_version_head; kv; kv = kv->next) { @@ -227,8 +238,14 @@ bool nfc_device_load_mifare_df_key_settings( string_printf(key, "%s Key Changeable", prefix); if(!flipper_format_read_bool(file, string_get_cstr(key), &ks->master_key_changeable, 1)) break; + string_printf(key, "%s Flags", prefix); + if(flipper_format_key_exist(file, string_get_cstr(key))) { + if(!flipper_format_read_hex(file, string_get_cstr(key), &ks->flags, 1)) break; + } string_printf(key, "%s Max Keys", prefix); if(!flipper_format_read_hex(file, string_get_cstr(key), &ks->max_keys, 1)) break; + ks->flags |= ks->max_keys >> 4; + ks->max_keys &= 0xF; MifareDesfireKeyVersion** kv_head = &ks->key_version_head; for(int key_id = 0; key_id < ks->max_keys; key_id++) { string_printf(key, "%s Key %d Version", prefix, key_id); @@ -624,6 +641,7 @@ static bool nfc_device_save_mifare_classic_data(FlipperFormat* file, NfcDevice* // Save Mifare Classic specific data do { if(!flipper_format_write_comment_cstr(file, "Mifare Classic specific data")) break; + if(data->type == MfClassicType1k) { if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "1K")) break; blocks = 64; @@ -631,8 +649,17 @@ static bool nfc_device_save_mifare_classic_data(FlipperFormat* file, NfcDevice* if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "4K")) break; blocks = 256; } - if(!flipper_format_write_comment_cstr(file, "Mifare Classic blocks")) break; + if(!flipper_format_write_uint32( + file, "Data format version", &nfc_mifare_classic_data_format_version, 1)) + break; + if(!flipper_format_write_comment_cstr( + file, "Key map is the bit mask indicating valid key in each sector")) + break; + if(!flipper_format_write_hex_uint64(file, "Key A map", &data->key_a_mask, 1)) break; + if(!flipper_format_write_hex_uint64(file, "Key B map", &data->key_b_mask, 1)) break; + + if(!flipper_format_write_comment_cstr(file, "Mifare Classic blocks")) break; bool block_saved = true; for(size_t i = 0; i < blocks; i++) { string_printf(temp_str, "Block %d", i); @@ -654,6 +681,7 @@ static bool nfc_device_load_mifare_classic_data(FlipperFormat* file, NfcDevice* bool parsed = false; MfClassicData* data = &dev->dev_data.mf_classic_data; string_t temp_str; + uint32_t data_format_version = 0; string_init(temp_str); uint16_t data_blocks = 0; @@ -669,6 +697,19 @@ static bool nfc_device_load_mifare_classic_data(FlipperFormat* file, NfcDevice* } else { break; } + + // Read Mifare Classic format version + if(!flipper_format_read_uint32(file, "Data format version", &data_format_version, 1)) { + // Load unread sectors with zero keys access for backward compatability + if(!flipper_format_rewind(file)) break; + data->key_a_mask = 0xffffffffffffffff; + data->key_b_mask = 0xffffffffffffffff; + } else { + if(data_format_version != nfc_mifare_classic_data_format_version) break; + if(!flipper_format_read_hex_uint64(file, "Key A map", &data->key_a_mask, 1)) break; + if(!flipper_format_read_hex_uint64(file, "Key B map", &data->key_b_mask, 1)) break; + } + // Read Mifare Classic blocks bool block_read = true; for(size_t i = 0; i < data_blocks; i++) { @@ -693,11 +734,24 @@ void nfc_device_set_name(NfcDevice* dev, const char* name) { strlcpy(dev->dev_name, name, NFC_DEV_NAME_MAX_LEN); } +static void nfc_device_get_path_without_ext(string_t orig_path, string_t shadow_path) { + // TODO: this won't work if there is ".nfc" anywhere in the path other than + // at the end + size_t ext_start = string_search_str(orig_path, NFC_APP_EXTENSION); + string_set_n(shadow_path, orig_path, 0, ext_start); +} + +static void nfc_device_get_shadow_path(string_t orig_path, string_t shadow_path) { + nfc_device_get_path_without_ext(orig_path, shadow_path); + string_cat_printf(shadow_path, "%s", NFC_APP_SHADOW_EXTENSION); +} + static bool nfc_device_save_file( NfcDevice* dev, const char* dev_name, const char* folder, - const char* extension) { + const char* extension, + bool use_load_path) { furi_assert(dev); bool saved = false; @@ -707,10 +761,19 @@ static bool nfc_device_save_file( string_init(temp_str); do { - // Create nfc directory if necessary - if(!storage_simply_mkdir(dev->storage, NFC_APP_FOLDER)) break; - // First remove nfc device file if it was saved - string_printf(temp_str, "%s/%s%s", folder, dev_name, extension); + if(use_load_path && !string_empty_p(dev->load_path)) { + // Get directory name + path_extract_dirname(string_get_cstr(dev->load_path), temp_str); + // Create nfc directory if necessary + if(!storage_simply_mkdir(dev->storage, string_get_cstr(temp_str))) break; + // Make path to file to save + string_cat_printf(temp_str, "/%s%s", dev_name, extension); + } else { + // Create nfc directory if necessary + if(!storage_simply_mkdir(dev->storage, NFC_APP_FOLDER)) break; + // First remove nfc device file if it was saved + string_printf(temp_str, "%s/%s%s", folder, dev_name, extension); + } // Open file if(!flipper_format_file_open_always(file, string_get_cstr(temp_str))) break; // Write header @@ -749,12 +812,12 @@ static bool nfc_device_save_file( } bool nfc_device_save(NfcDevice* dev, const char* dev_name) { - return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_EXTENSION); + return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_EXTENSION, true); } bool nfc_device_save_shadow(NfcDevice* dev, const char* dev_name) { dev->shadow_file_exist = true; - return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_SHADOW_EXTENSION); + return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_SHADOW_EXTENSION, true); } static bool nfc_device_load_data(NfcDevice* dev, string_t path) { @@ -768,9 +831,7 @@ static bool nfc_device_load_data(NfcDevice* dev, string_t path) { do { // Check existance of shadow file - size_t ext_start = string_search_str(path, NFC_APP_EXTENSION); - string_set_n(temp_str, path, 0, ext_start); - string_cat_printf(temp_str, "%s", NFC_APP_SHADOW_EXTENSION); + nfc_device_get_shadow_path(path, temp_str); dev->shadow_file_exist = storage_common_stat(dev->storage, string_get_cstr(temp_str), NULL) == FSE_OK; // Open shadow file if it exists. If not - open original @@ -827,15 +888,16 @@ bool nfc_device_load(NfcDevice* dev, const char* file_path) { furi_assert(file_path); // Load device data - string_t path; - string_init_set_str(path, file_path); - bool dev_load = nfc_device_load_data(dev, path); + string_set_str(dev->load_path, file_path); + bool dev_load = nfc_device_load_data(dev, dev->load_path); if(dev_load) { // Set device name - path_extract_filename_no_ext(file_path, path); - nfc_device_set_name(dev, string_get_cstr(path)); + string_t filename; + string_init(filename); + path_extract_filename_no_ext(file_path, filename); + nfc_device_set_name(dev, string_get_cstr(filename)); + string_clear(filename); } - string_clear(path); return dev_load; } @@ -843,23 +905,22 @@ bool nfc_device_load(NfcDevice* dev, const char* file_path) { bool nfc_file_select(NfcDevice* dev) { furi_assert(dev); - // Input events and views are managed by file_select - bool res = dialog_file_select_show( - dev->dialogs, - NFC_APP_FOLDER, - NFC_APP_EXTENSION, - dev->file_name, - sizeof(dev->file_name), - dev->dev_name); + // Input events and views are managed by file_browser + string_t nfc_app_folder; + string_init_set_str(nfc_app_folder, NFC_APP_FOLDER); + bool res = dialog_file_browser_show( + dev->dialogs, dev->load_path, nfc_app_folder, NFC_APP_EXTENSION, true, &I_Nfc_10px, true); + string_clear(nfc_app_folder); if(res) { - string_t dev_str; - // Get key file path - string_init_printf(dev_str, "%s/%s%s", NFC_APP_FOLDER, dev->file_name, NFC_APP_EXTENSION); - res = nfc_device_load_data(dev, dev_str); + string_t filename; + string_init(filename); + path_extract_filename(dev->load_path, filename, true); + strncpy(dev->dev_name, string_get_cstr(filename), NFC_DEV_NAME_MAX_LEN); + res = nfc_device_load_data(dev, dev->load_path); if(res) { - nfc_device_set_name(dev, dev->file_name); + nfc_device_set_name(dev, dev->dev_name); } - string_clear(dev_str); + string_clear(filename); } return res; @@ -877,9 +938,10 @@ void nfc_device_clear(NfcDevice* dev) { nfc_device_data_clear(&dev->dev_data); memset(&dev->dev_data, 0, sizeof(dev->dev_data)); dev->format = NfcDeviceSaveFormatUid; + string_reset(dev->load_path); } -bool nfc_device_delete(NfcDevice* dev) { +bool nfc_device_delete(NfcDevice* dev, bool use_load_path) { furi_assert(dev); bool deleted = false; @@ -888,12 +950,20 @@ bool nfc_device_delete(NfcDevice* dev) { do { // Delete original file - string_init_printf(file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); + if(use_load_path && !string_empty_p(dev->load_path)) { + string_set(file_path, dev->load_path); + } else { + string_printf(file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); + } if(!storage_simply_remove(dev->storage, string_get_cstr(file_path))) break; // Delete shadow file if it exists if(dev->shadow_file_exist) { - string_printf( - file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_EXTENSION); + if(use_load_path && !string_empty_p(dev->load_path)) { + nfc_device_get_shadow_path(dev->load_path, file_path); + } else { + string_printf( + file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_EXTENSION); + } if(!storage_simply_remove(dev->storage, string_get_cstr(file_path))) break; } deleted = true; @@ -907,19 +977,29 @@ bool nfc_device_delete(NfcDevice* dev) { return deleted; } -bool nfc_device_restore(NfcDevice* dev) { +bool nfc_device_restore(NfcDevice* dev, bool use_load_path) { furi_assert(dev); furi_assert(dev->shadow_file_exist); bool restored = false; string_t path; + string_init(path); + do { - string_init_printf( - path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_EXTENSION); + if(use_load_path && !string_empty_p(dev->load_path)) { + nfc_device_get_shadow_path(dev->load_path, path); + } else { + string_printf( + path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_EXTENSION); + } if(!storage_simply_remove(dev->storage, string_get_cstr(path))) break; dev->shadow_file_exist = false; - string_printf(path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); + if(use_load_path && !string_empty_p(dev->load_path)) { + string_set(path, dev->load_path); + } else { + string_printf(path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); + } if(!nfc_device_load_data(dev, path)) break; restored = true; } while(0); diff --git a/applications/nfc/nfc_device.h b/applications/nfc/nfc_device.h index 215f637f..400f6c36 100644 --- a/applications/nfc/nfc_device.h +++ b/applications/nfc/nfc_device.h @@ -12,7 +12,6 @@ #include #define NFC_DEV_NAME_MAX_LEN 22 -#define NFC_FILE_NAME_MAX_LEN 120 #define NFC_READER_DATA_MAX_SIZE 64 #define NFC_APP_FOLDER "/any/nfc" @@ -57,7 +56,7 @@ typedef struct { DialogsApp* dialogs; NfcDeviceData dev_data; char dev_name[NFC_DEV_NAME_MAX_LEN + 1]; - char file_name[NFC_FILE_NAME_MAX_LEN]; + string_t load_path; NfcDeviceSaveFormat format; bool shadow_file_exist; } NfcDevice; @@ -80,6 +79,6 @@ void nfc_device_data_clear(NfcDeviceData* dev); void nfc_device_clear(NfcDevice* dev); -bool nfc_device_delete(NfcDevice* dev); +bool nfc_device_delete(NfcDevice* dev, bool use_load_path); -bool nfc_device_restore(NfcDevice* dev); +bool nfc_device_restore(NfcDevice* dev, bool use_load_path); diff --git a/applications/nfc/nfc_types.c b/applications/nfc/nfc_types.c index 1b67284c..2d11c339 100644 --- a/applications/nfc/nfc_types.c +++ b/applications/nfc/nfc_types.c @@ -35,6 +35,14 @@ const char* nfc_mf_ul_type(MfUltralightType type, bool full_name) { return "NTAG215"; } else if(type == MfUltralightTypeNTAG216) { return "NTAG216"; + } else if(type == MfUltralightTypeNTAGI2C1K) { + return "NTAG I2C 1K"; + } else if(type == MfUltralightTypeNTAGI2C2K) { + return "NTAG I2C 2K"; + } else if(type == MfUltralightTypeNTAGI2CPlus1K) { + return "NTAG I2C Plus 1K"; + } else if(type == MfUltralightTypeNTAGI2CPlus2K) { + return "NTAG I2C Plus 2K"; } else if(type == MfUltralightTypeUL11 && full_name) { return "Mifare Ultralight 11"; } else if(type == MfUltralightTypeUL21 && full_name) { diff --git a/applications/nfc/nfc_worker.c b/applications/nfc/nfc_worker.c index 6b3c8f09..88729bf4 100644 --- a/applications/nfc/nfc_worker.c +++ b/applications/nfc/nfc_worker.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "helpers/nfc_mf_classic_dict.h" @@ -104,6 +105,8 @@ int32_t nfc_worker_task(void* context) { nfc_worker_emulate_mifare_ul(nfc_worker); } else if(nfc_worker->state == NfcWorkerStateReadMifareClassic) { nfc_worker_mifare_classic_dict_attack(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateEmulateMifareClassic) { + nfc_worker_emulate_mifare_classic(nfc_worker); } else if(nfc_worker->state == NfcWorkerStateReadMifareDesfire) { nfc_worker_read_mifare_desfire(nfc_worker); } @@ -312,6 +315,11 @@ void nfc_worker_emulate_mifare_ul(NfcWorker* nfc_worker) { MfUltralightEmulator emulator = {}; mf_ul_prepare_emulation(&emulator, &nfc_worker->dev_data->mf_ul_data); while(nfc_worker->state == NfcWorkerStateEmulateMifareUltralight) { + emulator.auth_success = false; + if(emulator.data.type >= MfUltralightTypeNTAGI2C1K) { + // Sector index needs to be reset + emulator.curr_sector = 0; + } furi_hal_nfc_emulate_nfca( nfc_data->uid, nfc_data->uid_len, @@ -474,6 +482,34 @@ void nfc_worker_mifare_classic_dict_attack(NfcWorker* nfc_worker) { stream_free(nfc_worker->dict_stream); } +void nfc_worker_emulate_mifare_classic(NfcWorker* nfc_worker) { + FuriHalNfcTxRxContext tx_rx; + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + MfClassicEmulator emulator = { + .cuid = nfc_util_bytes2num(&nfc_data->uid[nfc_data->uid_len - 4], 4), + .data = nfc_worker->dev_data->mf_classic_data, + .data_changed = false, + }; + NfcaSignal* nfca_signal = nfca_signal_alloc(); + tx_rx.nfca_signal = nfca_signal; + + while(nfc_worker->state == NfcWorkerStateEmulateMifareClassic) { + if(furi_hal_nfc_listen( + nfc_data->uid, nfc_data->uid_len, nfc_data->atqa, nfc_data->sak, true, 300)) { + mf_classic_emulator(&emulator, &tx_rx); + } + } + if(emulator.data_changed) { + nfc_worker->dev_data->mf_classic_data = emulator.data; + if(nfc_worker->callback) { + nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context); + } + emulator.data_changed = false; + } + + nfca_signal_free(nfca_signal); +} + void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker) { ReturnCode err; uint8_t tx_buff[64] = {}; diff --git a/applications/nfc/nfc_worker.h b/applications/nfc/nfc_worker.h index 1933a79b..a68f42d7 100755 --- a/applications/nfc/nfc_worker.h +++ b/applications/nfc/nfc_worker.h @@ -19,6 +19,7 @@ typedef enum { NfcWorkerStateReadMifareUltralight, NfcWorkerStateEmulateMifareUltralight, NfcWorkerStateReadMifareClassic, + NfcWorkerStateEmulateMifareClassic, NfcWorkerStateReadMifareDesfire, // Transition NfcWorkerStateStop, diff --git a/applications/nfc/scenes/nfc_scene_config.h b/applications/nfc/scenes/nfc_scene_config.h index 6b5d5d10..e6351d42 100755 --- a/applications/nfc/scenes/nfc_scene_config.h +++ b/applications/nfc/scenes/nfc_scene_config.h @@ -34,4 +34,6 @@ ADD_SCENE(nfc, restore_original, RestoreOriginal) ADD_SCENE(nfc, debug, Debug) ADD_SCENE(nfc, field, Field) ADD_SCENE(nfc, read_mifare_classic, ReadMifareClassic) +ADD_SCENE(nfc, emulate_mifare_classic, EmulateMifareClassic) +ADD_SCENE(nfc, mifare_classic_menu, MifareClassicMenu) ADD_SCENE(nfc, dict_not_found, DictNotFound) diff --git a/applications/nfc/scenes/nfc_scene_delete.c b/applications/nfc/scenes/nfc_scene_delete.c index e8ba3e44..1946b929 100755 --- a/applications/nfc/scenes/nfc_scene_delete.c +++ b/applications/nfc/scenes/nfc_scene_delete.c @@ -73,7 +73,7 @@ bool nfc_scene_delete_on_event(void* context, SceneManagerEvent event) { if(event.event == GuiButtonTypeLeft) { return scene_manager_previous_scene(nfc->scene_manager); } else if(event.event == GuiButtonTypeRight) { - if(nfc_device_delete(nfc->dev)) { + if(nfc_device_delete(nfc->dev, true)) { scene_manager_next_scene(nfc->scene_manager, NfcSceneDeleteSuccess); } else { scene_manager_search_and_switch_to_previous_scene( diff --git a/applications/nfc/scenes/nfc_scene_emulate_mifare_classic.c b/applications/nfc/scenes/nfc_scene_emulate_mifare_classic.c new file mode 100644 index 00000000..1286024c --- /dev/null +++ b/applications/nfc/scenes/nfc_scene_emulate_mifare_classic.c @@ -0,0 +1,64 @@ +#include "../nfc_i.h" +#include + +#define NFC_MF_CLASSIC_DATA_NOT_CHANGED (0UL) +#define NFC_MF_CLASSIC_DATA_CHANGED (1UL) + +void nfc_emulate_mifare_classic_worker_callback(NfcWorkerEvent event, void* context) { + UNUSED(event); + Nfc* nfc = context; + + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneEmulateMifareClassic, NFC_MF_CLASSIC_DATA_CHANGED); +} + +void nfc_scene_emulate_mifare_classic_on_enter(void* context) { + Nfc* nfc = context; + DOLPHIN_DEED(DolphinDeedNfcEmulate); + + // Setup view + Popup* popup = nfc->popup; + if(strcmp(nfc->dev->dev_name, "")) { + nfc_text_store_set(nfc, "%s", nfc->dev->dev_name); + } + popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61); + popup_set_header(popup, "Emulating\nMf Classic", 56, 31, AlignLeft, AlignTop); + + // Setup and start worker + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); + nfc_worker_start( + nfc->worker, + NfcWorkerStateEmulateMifareClassic, + &nfc->dev->dev_data, + nfc_emulate_mifare_classic_worker_callback, + nfc); +} + +bool nfc_scene_emulate_mifare_classic_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeTick) { + notification_message(nfc->notifications, &sequence_blink_blue_10); + consumed = true; + } else if(event.type == SceneManagerEventTypeBack) { + // Stop worker + nfc_worker_stop(nfc->worker); + // Check if data changed and save in shadow file + if(scene_manager_get_scene_state(nfc->scene_manager, NfcSceneEmulateMifareClassic) == + NFC_MF_CLASSIC_DATA_CHANGED) { + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneEmulateMifareClassic, NFC_MF_CLASSIC_DATA_NOT_CHANGED); + nfc_device_save_shadow(nfc->dev, nfc->dev->dev_name); + } + consumed = false; + } + return consumed; +} + +void nfc_scene_emulate_mifare_classic_on_exit(void* context) { + Nfc* nfc = context; + + // Clear view + popup_reset(nfc->popup); +} diff --git a/applications/nfc/scenes/nfc_scene_mifare_classic_menu.c b/applications/nfc/scenes/nfc_scene_mifare_classic_menu.c new file mode 100644 index 00000000..4611d16b --- /dev/null +++ b/applications/nfc/scenes/nfc_scene_mifare_classic_menu.c @@ -0,0 +1,64 @@ +#include "../nfc_i.h" + +enum SubmenuIndex { + SubmenuIndexSave, + SubmenuIndexEmulate, +}; + +void nfc_scene_mifare_classic_menu_submenu_callback(void* context, uint32_t index) { + Nfc* nfc = context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, index); +} + +void nfc_scene_mifare_classic_menu_on_enter(void* context) { + Nfc* nfc = context; + Submenu* submenu = nfc->submenu; + + submenu_add_item( + submenu, "Save", SubmenuIndexSave, nfc_scene_mifare_classic_menu_submenu_callback, nfc); + submenu_add_item( + submenu, + "Emulate", + SubmenuIndexEmulate, + nfc_scene_mifare_classic_menu_submenu_callback, + nfc); + submenu_set_selected_item( + nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMifareUlMenu)); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_mifare_classic_menu_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexSave) { + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneMifareUlMenu, SubmenuIndexSave); + nfc->dev->format = NfcDeviceSaveFormatMifareClassic; + // Clear device name + nfc_device_set_name(nfc->dev, ""); + scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); + consumed = true; + } else if(event.event == SubmenuIndexEmulate) { + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneMifareUlMenu, SubmenuIndexEmulate); + scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareClassic); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = + scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneStart); + } + + return consumed; +} + +void nfc_scene_mifare_classic_menu_on_exit(void* context) { + Nfc* nfc = context; + + // Clear view + submenu_reset(nfc->submenu); +} diff --git a/applications/nfc/scenes/nfc_scene_read_mifare_classic.c b/applications/nfc/scenes/nfc_scene_read_mifare_classic.c index c6ce4c2d..c4422285 100644 --- a/applications/nfc/scenes/nfc_scene_read_mifare_classic.c +++ b/applications/nfc/scenes/nfc_scene_read_mifare_classic.c @@ -47,7 +47,7 @@ bool nfc_scene_read_mifare_classic_on_event(void* context, SceneManagerEvent eve consumed = true; } else if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventDictAttackDone) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); + scene_manager_next_scene(nfc->scene_manager, NfcSceneMifareClassicMenu); consumed = true; } else if(event.event == NfcWorkerEventDetectedClassic1k) { dict_attack_card_detected(nfc->dict_attack, MfClassicType1k); @@ -71,7 +71,6 @@ bool nfc_scene_read_mifare_classic_on_event(void* context, SceneManagerEvent eve scene_manager_set_scene_state( nfc->scene_manager, NfcSceneReadMifareClassic, NfcSceneReadMifareClassicStateDone); notification_message(nfc->notifications, &sequence_success); - nfc->dev->format = NfcDeviceSaveFormatMifareClassic; dict_attack_set_result(nfc->dict_attack, true); consumed = true; } else if(event.event == NfcWorkerEventFail) { diff --git a/applications/nfc/scenes/nfc_scene_save_name.c b/applications/nfc/scenes/nfc_scene_save_name.c old mode 100755 new mode 100644 index e95c97eb..d5e05472 --- a/applications/nfc/scenes/nfc_scene_save_name.c +++ b/applications/nfc/scenes/nfc_scene_save_name.c @@ -1,6 +1,8 @@ #include "../nfc_i.h" +#include "m-string.h" #include #include +#include void nfc_scene_save_name_text_input_callback(void* context) { Nfc* nfc = context; @@ -29,11 +31,22 @@ void nfc_scene_save_name_on_enter(void* context) { NFC_DEV_NAME_MAX_LEN, dev_name_empty); - ValidatorIsFile* validator_is_file = - validator_is_file_alloc_init(NFC_APP_FOLDER, NFC_APP_EXTENSION, nfc->dev->dev_name); + string_t folder_path; + string_init(folder_path); + + if(string_end_with_str_p(nfc->dev->load_path, NFC_APP_EXTENSION)) { + path_extract_dirname(string_get_cstr(nfc->dev->load_path), folder_path); + } else { + string_set_str(folder_path, NFC_APP_FOLDER); + } + + ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( + string_get_cstr(folder_path), NFC_APP_EXTENSION, nfc->dev->dev_name); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextInput); + + string_clear(folder_path); } bool nfc_scene_save_name_on_event(void* context, SceneManagerEvent event) { @@ -43,7 +56,7 @@ bool nfc_scene_save_name_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventTextInputDone) { if(strcmp(nfc->dev->dev_name, "")) { - nfc_device_delete(nfc->dev); + nfc_device_delete(nfc->dev, true); } if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetUid)) { nfc->dev->dev_data.nfc_data = nfc->dev_edit_data; diff --git a/applications/nfc/scenes/nfc_scene_save_success.c b/applications/nfc/scenes/nfc_scene_save_success.c index 985897a6..5c15a509 100644 --- a/applications/nfc/scenes/nfc_scene_save_success.c +++ b/applications/nfc/scenes/nfc_scene_save_success.c @@ -30,9 +30,6 @@ bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) { if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneCardMenu)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneCardMenu); - } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { - consumed = scene_manager_search_and_switch_to_another_scene( - nfc->scene_manager, NfcSceneFileSelect); } else if(scene_manager_has_previous_scene( nfc->scene_manager, NfcSceneMifareDesfireMenu)) { consumed = scene_manager_search_and_switch_to_previous_scene( diff --git a/applications/nfc/scenes/nfc_scene_saved_menu.c b/applications/nfc/scenes/nfc_scene_saved_menu.c index e0489c15..f2b2dea3 100644 --- a/applications/nfc/scenes/nfc_scene_saved_menu.c +++ b/applications/nfc/scenes/nfc_scene_saved_menu.c @@ -27,13 +27,11 @@ void nfc_scene_saved_menu_on_enter(void* context) { SubmenuIndexEmulate, nfc_scene_saved_menu_submenu_callback, nfc); - } else if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { + } else if( + nfc->dev->format == NfcDeviceSaveFormatMifareUl || + nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { submenu_add_item( - submenu, - "Emulate Ultralight", - SubmenuIndexEmulate, - nfc_scene_saved_menu_submenu_callback, - nfc); + submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_saved_menu_submenu_callback, nfc); } submenu_add_item( submenu, "Edit UID and Name", SubmenuIndexEdit, nfc_scene_saved_menu_submenu_callback, nfc); @@ -64,6 +62,8 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { if(event.event == SubmenuIndexEmulate) { if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareUl); + } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareClassic); } else { scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); } @@ -78,7 +78,7 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(nfc->scene_manager, NfcSceneDeviceInfo); consumed = true; } else if(event.event == SubmenuIndexRestoreOriginal) { - if(!nfc_device_restore(nfc->dev)) { + if(!nfc_device_restore(nfc->dev, true)) { scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneStart); } else { diff --git a/applications/nfc/scenes/nfc_scene_set_type.c b/applications/nfc/scenes/nfc_scene_set_type.c index 0dbb4f7e..0fe63424 100755 --- a/applications/nfc/scenes/nfc_scene_set_type.c +++ b/applications/nfc/scenes/nfc_scene_set_type.c @@ -1,4 +1,5 @@ #include "../nfc_i.h" +#include "m-string.h" enum SubmenuIndex { SubmenuIndexNFCA4, @@ -16,6 +17,7 @@ void nfc_scene_set_type_on_enter(void* context) { Submenu* submenu = nfc->submenu; // Clear device name nfc_device_set_name(nfc->dev, ""); + string_set_str(nfc->dev->load_path, ""); submenu_add_item( submenu, "NFC-A 7-bytes UID", SubmenuIndexNFCA7, nfc_scene_set_type_submenu_callback, nfc); submenu_add_item( diff --git a/applications/nfc/views/dict_attack.c b/applications/nfc/views/dict_attack.c index 83a56b0b..0f9da494 100644 --- a/applications/nfc/views/dict_attack.c +++ b/applications/nfc/views/dict_attack.c @@ -46,7 +46,7 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, draw_str); } else if(m->state == DictAttackStateSuccess) { canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, "Complete!"); - elements_button_right(canvas, "Save"); + elements_button_right(canvas, "More"); } else if(m->state == DictAttackStateFail) { canvas_draw_str_aligned( canvas, 64, 2, AlignCenter, AlignTop, "Failed to read any sector"); diff --git a/applications/power/power_service/power.c b/applications/power/power_service/power.c index ac1856e7..1315809e 100644 --- a/applications/power/power_service/power.c +++ b/applications/power/power_service/power.c @@ -168,10 +168,24 @@ static void power_check_low_battery(Power* power) { } // If battery low, update view and switch off power after timeout if(power->battery_low) { - if(power->power_off_timeout) { - power_off_set_time_left(power->power_off, power->power_off_timeout--); - } else { + PowerOffResponse response = power_off_get_response(power->power_off); + if(response == PowerOffResponseDefault) { + if(power->power_off_timeout) { + power_off_set_time_left(power->power_off, power->power_off_timeout--); + } else { + power_off(power); + } + } else if(response == PowerOffResponseOk) { power_off(power); + } else if(response == PowerOffResponseHide) { + view_dispatcher_switch_to_view(power->view_dispatcher, VIEW_NONE); + if(power->power_off_timeout) { + power_off_set_time_left(power->power_off, power->power_off_timeout--); + } else { + power_off(power); + } + } else if(response == PowerOffResponseCancel) { + view_dispatcher_switch_to_view(power->view_dispatcher, VIEW_NONE); } } } diff --git a/applications/power/power_service/views/power_off.c b/applications/power/power_service/views/power_off.c old mode 100755 new mode 100644 index 46344b9b..398ebe4a --- a/applications/power/power_service/views/power_off.c +++ b/applications/power/power_service/views/power_off.c @@ -7,6 +7,7 @@ struct PowerOff { }; typedef struct { + PowerOffResponse response; uint32_t time_left_sec; } PowerOffModel; @@ -21,18 +22,54 @@ static void power_off_draw_callback(Canvas* canvas, void* _model) { canvas_draw_icon(canvas, 0, 18, &I_BatteryBody_52x28); canvas_draw_icon(canvas, 16, 25, &I_FaceNopower_29x14); elements_bubble(canvas, 54, 17, 70, 30); + canvas_set_font(canvas, FontSecondary); - elements_multiline_text_aligned( - canvas, 70, 23, AlignLeft, AlignTop, "Connect me\n to charger."); - snprintf(buff, sizeof(buff), "Poweroff in %lds.", model->time_left_sec); - canvas_draw_str_aligned(canvas, 64, 60, AlignCenter, AlignBottom, buff); + if(model->response == PowerOffResponseDefault) { + snprintf(buff, sizeof(buff), "Charge me!\nOff in %lds!", model->time_left_sec); + elements_multiline_text_aligned(canvas, 70, 23, AlignLeft, AlignTop, buff); + + elements_button_left(canvas, "Cancel"); + elements_button_center(canvas, "OK"); + elements_button_right(canvas, "Hide"); + } else { + snprintf(buff, sizeof(buff), "Charge me!\nDont't forget!"); + elements_multiline_text_aligned(canvas, 70, 23, AlignLeft, AlignTop, buff); + + canvas_draw_str_aligned(canvas, 64, 60, AlignCenter, AlignBottom, "Hold a second..."); + } +} + +static bool power_off_input_callback(InputEvent* event, void* context) { + PowerOff* power_off = context; + + bool consumed = false; + PowerOffModel* model = view_get_model(power_off->view); + if(model->response == PowerOffResponseDefault && event->type == InputTypeShort) { + if(event->key == InputKeyOk) { + model->response = PowerOffResponseOk; + consumed = true; + } else if(event->key == InputKeyLeft) { + model->response = PowerOffResponseCancel; + consumed = true; + } else if(event->key == InputKeyRight) { + model->response = PowerOffResponseHide; + consumed = true; + } + } + view_commit_model(power_off->view, consumed); + + return true; } PowerOff* power_off_alloc() { PowerOff* power_off = malloc(sizeof(PowerOff)); + power_off->view = view_alloc(); view_allocate_model(power_off->view, ViewModelTypeLocking, sizeof(PowerOffModel)); + view_set_context(power_off->view, power_off); view_set_draw_callback(power_off->view, power_off_draw_callback); + view_set_input_callback(power_off->view, power_off_input_callback); + return power_off; } @@ -55,3 +92,14 @@ void power_off_set_time_left(PowerOff* power_off, uint8_t time_left) { return true; }); } + +PowerOffResponse power_off_get_response(PowerOff* power_off) { + furi_assert(power_off); + PowerOffResponse response; + with_view_model( + power_off->view, (PowerOffModel * model) { + response = model->response; + return false; + }); + return response; +} diff --git a/applications/power/power_service/views/power_off.h b/applications/power/power_service/views/power_off.h index 2e2e91f7..5137c2e9 100644 --- a/applications/power/power_service/views/power_off.h +++ b/applications/power/power_service/views/power_off.h @@ -2,6 +2,13 @@ typedef struct PowerOff PowerOff; +typedef enum { + PowerOffResponseDefault, + PowerOffResponseOk, + PowerOffResponseCancel, + PowerOffResponseHide, +} PowerOffResponse; + #include PowerOff* power_off_alloc(); @@ -11,3 +18,5 @@ void power_off_free(PowerOff* power_off); View* power_off_get_view(PowerOff* power_off); void power_off_set_time_left(PowerOff* power_off, uint8_t time_left); + +PowerOffResponse power_off_get_response(PowerOff* power_off); diff --git a/applications/scened_app_example/scene/scened_app_scene_byte_input.cpp b/applications/scened_app_example/scene/scened_app_scene_byte_input.cpp deleted file mode 100644 index 200252ad..00000000 --- a/applications/scened_app_example/scene/scened_app_scene_byte_input.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "scened_app_scene_byte_input.h" - -void ScenedAppSceneByteInput::on_enter(ScenedApp* app, bool /* need_restore */) { - ByteInputVM* byte_input = app->view_controller; - auto callback = cbc::obtain_connector(this, &ScenedAppSceneByteInput::result_callback); - - byte_input->set_result_callback(callback, NULL, app, data, 4); - byte_input->set_header_text("Enter the key"); - - app->view_controller.switch_to(); -} - -bool ScenedAppSceneByteInput::on_event(ScenedApp* app, ScenedApp::Event* event) { - bool consumed = false; - - if(event->type == ScenedApp::EventType::ByteEditResult) { - app->scene_controller.switch_to_previous_scene(); - consumed = true; - } - - return consumed; -} - -void ScenedAppSceneByteInput::on_exit(ScenedApp* app) { - app->view_controller.get()->clean(); -} - -void ScenedAppSceneByteInput::result_callback(void* context) { - ScenedApp* app = static_cast(context); - ScenedApp::Event event; - - event.type = ScenedApp::EventType::ByteEditResult; - - app->view_controller.send_event(&event); -} diff --git a/applications/scened_app_example/scene/scened_app_scene_byte_input.h b/applications/scened_app_example/scene/scened_app_scene_byte_input.h deleted file mode 100644 index 0f0b02ac..00000000 --- a/applications/scened_app_example/scene/scened_app_scene_byte_input.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once -#include "../scened_app.h" - -class ScenedAppSceneByteInput : public GenericScene { -public: - void on_enter(ScenedApp* app, bool need_restore) final; - bool on_event(ScenedApp* app, ScenedApp::Event* event) final; - void on_exit(ScenedApp* app) final; - -private: - void result_callback(void* context); - - uint8_t data[4] = { - 0x01, - 0xA2, - 0xF4, - 0xD3, - }; -}; diff --git a/applications/scened_app_example/scene/scened_app_scene_start.cpp b/applications/scened_app_example/scene/scened_app_scene_start.cpp deleted file mode 100644 index 5538962b..00000000 --- a/applications/scened_app_example/scene/scened_app_scene_start.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "scened_app_scene_start.h" - -typedef enum { - SubmenuByteInput, -} SubmenuIndex; - -void ScenedAppSceneStart::on_enter(ScenedApp* app, bool need_restore) { - auto submenu = app->view_controller.get(); - auto callback = cbc::obtain_connector(this, &ScenedAppSceneStart::submenu_callback); - - submenu->add_item("Byte Input", SubmenuByteInput, callback, app); - - if(need_restore) { - submenu->set_selected_item(submenu_item_selected); - } - app->view_controller.switch_to(); -} - -bool ScenedAppSceneStart::on_event(ScenedApp* app, ScenedApp::Event* event) { - bool consumed = false; - - if(event->type == ScenedApp::EventType::MenuSelected) { - submenu_item_selected = event->payload.menu_index; - switch(event->payload.menu_index) { - case SubmenuByteInput: - app->scene_controller.switch_to_next_scene(ScenedApp::SceneType::ByteInputScene); - break; - } - consumed = true; - } - - return consumed; -} - -void ScenedAppSceneStart::on_exit(ScenedApp* app) { - app->view_controller.get()->clean(); -} - -void ScenedAppSceneStart::submenu_callback(void* context, uint32_t index) { - ScenedApp* app = static_cast(context); - ScenedApp::Event event; - - event.type = ScenedApp::EventType::MenuSelected; - event.payload.menu_index = index; - - app->view_controller.send_event(&event); -} diff --git a/applications/scened_app_example/scene/scened_app_scene_start.h b/applications/scened_app_example/scene/scened_app_scene_start.h deleted file mode 100644 index 8324a20f..00000000 --- a/applications/scened_app_example/scene/scened_app_scene_start.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once -#include "../scened_app.h" - -class ScenedAppSceneStart : public GenericScene { -public: - void on_enter(ScenedApp* app, bool need_restore) final; - bool on_event(ScenedApp* app, ScenedApp::Event* event) final; - void on_exit(ScenedApp* app) final; - -private: - void submenu_callback(void* context, uint32_t index); - uint32_t submenu_item_selected = 0; -}; diff --git a/applications/scened_app_example/scened_app.cpp b/applications/scened_app_example/scened_app.cpp deleted file mode 100644 index 64040b22..00000000 --- a/applications/scened_app_example/scened_app.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "scened_app.h" -#include "scene/scened_app_scene_start.h" -#include "scene/scened_app_scene_byte_input.h" - -ScenedApp::ScenedApp() - : scene_controller{this} - , text_store{128} - , notification{"notification"} { -} - -ScenedApp::~ScenedApp() { -} - -void ScenedApp::run() { - scene_controller.add_scene(SceneType::Start, new ScenedAppSceneStart()); - scene_controller.add_scene(SceneType::ByteInputScene, new ScenedAppSceneByteInput()); - - notification_message(notification, &sequence_blink_green_10); - scene_controller.process(100); -} diff --git a/applications/scened_app_example/scened_app.h b/applications/scened_app_example/scened_app.h deleted file mode 100644 index c6cecb47..00000000 --- a/applications/scened_app_example/scened_app.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once -#include -#include - -#include -#include -#include -#include -#include - -#include -#include - -#include - -class ScenedApp { -public: - enum class EventType : uint8_t { - GENERIC_EVENT_ENUM_VALUES, - MenuSelected, - ByteEditResult, - }; - - enum class SceneType : uint8_t { - GENERIC_SCENE_ENUM_VALUES, - ByteInputScene, - }; - - class Event { - public: - union { - int32_t menu_index; - } payload; - - EventType type; - }; - - SceneController, ScenedApp> scene_controller; - TextStore text_store; - ViewController view_controller; - RecordController notification; - - ~ScenedApp(); - ScenedApp(); - - void run(); -}; diff --git a/applications/scened_app_example/scened_app_launcher.cpp b/applications/scened_app_example/scened_app_launcher.cpp deleted file mode 100644 index 3d0bdfca..00000000 --- a/applications/scened_app_example/scened_app_launcher.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "scened_app.h" - -// app enter function -extern "C" int32_t scened_app(void* p) { - UNUSED(p); - ScenedApp* app = new ScenedApp(); - app->run(); - delete app; - - return 0; -} diff --git a/applications/storage/storage_cli.c b/applications/storage/storage_cli.c index b35e37a8..4ce91770 100644 --- a/applications/storage/storage_cli.c +++ b/applications/storage/storage_cli.c @@ -593,7 +593,7 @@ static void storage_cli_factory_reset(Cli* cli, string_t args, void* context) { void storage_on_system_start() { #ifdef SRV_CLI Cli* cli = furi_record_open("cli"); - cli_add_command(cli, "storage", CliCommandFlagDefault, storage_cli, NULL); + cli_add_command(cli, "storage", CliCommandFlagParallelSafe, storage_cli, NULL); cli_add_command( cli, "factory_reset", CliCommandFlagParallelSafe, storage_cli_factory_reset, NULL); furi_record_close("cli"); diff --git a/applications/storage_settings/scenes/storage_settings_scene_benchmark.c b/applications/storage_settings/scenes/storage_settings_scene_benchmark.c index e7cfd826..45fbce8f 100644 --- a/applications/storage_settings/scenes/storage_settings_scene_benchmark.c +++ b/applications/storage_settings/scenes/storage_settings_scene_benchmark.c @@ -151,13 +151,7 @@ void storage_settings_scene_benchmark_on_exit(void* context) { StorageSettings* app = context; DialogEx* dialog_ex = app->dialog_ex; - dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); - dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); - dialog_ex_set_icon(dialog_ex, 0, 0, NULL); - dialog_ex_set_left_button_text(dialog_ex, NULL); - dialog_ex_set_right_button_text(dialog_ex, NULL); - dialog_ex_set_result_callback(dialog_ex, NULL); - dialog_ex_set_context(dialog_ex, NULL); + dialog_ex_reset(dialog_ex); string_reset(app->text_string); } diff --git a/applications/storage_settings/scenes/storage_settings_scene_factory_reset.c b/applications/storage_settings/scenes/storage_settings_scene_factory_reset.c index 78a8363a..84119422 100644 --- a/applications/storage_settings/scenes/storage_settings_scene_factory_reset.c +++ b/applications/storage_settings/scenes/storage_settings_scene_factory_reset.c @@ -18,7 +18,7 @@ void storage_settings_scene_factory_reset_on_enter(void* context) { dialog_ex_set_context(dialog_ex, app); dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_factory_reset_dialog_callback); - dialog_ex_set_left_button_text(dialog_ex, "Back"); + dialog_ex_set_left_button_text(dialog_ex, "Cancel"); dialog_ex_set_right_button_text(dialog_ex, "Erase"); dialog_ex_set_header(dialog_ex, "Confirm Factory Reset", 64, 10, AlignCenter, AlignCenter); @@ -70,7 +70,10 @@ bool storage_settings_scene_factory_reset_on_event(void* context, SceneManagerEv consumed = true; break; } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = true; } + return consumed; } @@ -78,13 +81,7 @@ void storage_settings_scene_factory_reset_on_exit(void* context) { StorageSettings* app = context; DialogEx* dialog_ex = app->dialog_ex; - dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); - dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); - dialog_ex_set_icon(dialog_ex, 0, 0, NULL); - dialog_ex_set_left_button_text(dialog_ex, NULL); - dialog_ex_set_right_button_text(dialog_ex, NULL); - dialog_ex_set_result_callback(dialog_ex, NULL); - dialog_ex_set_context(dialog_ex, NULL); + dialog_ex_reset(dialog_ex); string_reset(app->text_string); } diff --git a/applications/storage_settings/scenes/storage_settings_scene_format_confirm.c b/applications/storage_settings/scenes/storage_settings_scene_format_confirm.c index a80215bc..4a83bd51 100644 --- a/applications/storage_settings/scenes/storage_settings_scene_format_confirm.c +++ b/applications/storage_settings/scenes/storage_settings_scene_format_confirm.c @@ -11,7 +11,7 @@ void storage_settings_scene_format_confirm_on_enter(void* context) { StorageSettings* app = context; FS_Error sd_status = storage_sd_status(app->fs_api); DialogEx* dialog_ex = app->dialog_ex; - dialog_ex_set_left_button_text(dialog_ex, "Back"); + dialog_ex_set_left_button_text(dialog_ex, "Cancel"); if(sd_status == FSE_NOT_READY) { dialog_ex_set_header(dialog_ex, "SD card not mounted", 64, 10, AlignCenter, AlignCenter); @@ -57,11 +57,5 @@ void storage_settings_scene_format_confirm_on_exit(void* context) { StorageSettings* app = context; DialogEx* dialog_ex = app->dialog_ex; - dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); - dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); - dialog_ex_set_icon(dialog_ex, 0, 0, NULL); - dialog_ex_set_left_button_text(dialog_ex, NULL); - dialog_ex_set_right_button_text(dialog_ex, NULL); - dialog_ex_set_result_callback(dialog_ex, NULL); - dialog_ex_set_context(dialog_ex, NULL); + dialog_ex_reset(dialog_ex); } diff --git a/applications/storage_settings/scenes/storage_settings_scene_formatting.c b/applications/storage_settings/scenes/storage_settings_scene_formatting.c index 143bda95..2cbf97ee 100755 --- a/applications/storage_settings/scenes/storage_settings_scene_formatting.c +++ b/applications/storage_settings/scenes/storage_settings_scene_formatting.c @@ -39,18 +39,17 @@ void storage_settings_scene_formatting_on_enter(void* context) { notification_message(app->notification, &sequence_reset_formatting_leds); notification_message(app->notification, &sequence_blink_green_100); + dialog_ex_set_context(dialog_ex, app); + dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_formatting_dialog_callback); + if(error != FSE_OK) { dialog_ex_set_header(dialog_ex, "Cannot format SD Card", 64, 10, AlignCenter, AlignCenter); dialog_ex_set_text( dialog_ex, storage_error_get_desc(error), 64, 32, AlignCenter, AlignCenter); } else { - dialog_ex_set_header(dialog_ex, "SD card formatted", 64, 10, AlignCenter, AlignCenter); - dialog_ex_set_text(dialog_ex, "Press back to return", 64, 32, AlignCenter, AlignCenter); + dialog_ex_set_header(dialog_ex, "Format complete!", 64, 32, AlignCenter, AlignCenter); } - - dialog_ex_set_context(dialog_ex, app); - dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_formatting_dialog_callback); - dialog_ex_set_left_button_text(dialog_ex, "Back"); + dialog_ex_set_center_button_text(dialog_ex, "OK"); } bool storage_settings_scene_formatting_on_event(void* context, SceneManagerEvent event) { @@ -59,14 +58,13 @@ bool storage_settings_scene_formatting_on_event(void* context, SceneManagerEvent if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case DialogExResultLeft: + case DialogExResultCenter: consumed = scene_manager_search_and_switch_to_previous_scene( app->scene_manager, StorageSettingsStart); break; } } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_search_and_switch_to_previous_scene( - app->scene_manager, StorageSettingsStart); + consumed = true; } return consumed; @@ -76,11 +74,5 @@ void storage_settings_scene_formatting_on_exit(void* context) { StorageSettings* app = context; DialogEx* dialog_ex = app->dialog_ex; - dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); - dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); - dialog_ex_set_icon(dialog_ex, 0, 0, NULL); - dialog_ex_set_left_button_text(dialog_ex, NULL); - dialog_ex_set_right_button_text(dialog_ex, NULL); - dialog_ex_set_result_callback(dialog_ex, NULL); - dialog_ex_set_context(dialog_ex, NULL); + dialog_ex_reset(dialog_ex); } diff --git a/applications/storage_settings/scenes/storage_settings_scene_internal_info.c b/applications/storage_settings/scenes/storage_settings_scene_internal_info.c index 971695b6..53f791bd 100644 --- a/applications/storage_settings/scenes/storage_settings_scene_internal_info.c +++ b/applications/storage_settings/scenes/storage_settings_scene_internal_info.c @@ -18,7 +18,6 @@ void storage_settings_scene_internal_info_on_enter(void* context) { dialog_ex_set_context(dialog_ex, app); dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_internal_info_dialog_callback); - dialog_ex_set_left_button_text(dialog_ex, "Back"); if(error != FSE_OK) { dialog_ex_set_header( dialog_ex, "Internal storage error", 64, 10, AlignCenter, AlignCenter); @@ -56,13 +55,7 @@ void storage_settings_scene_internal_info_on_exit(void* context) { StorageSettings* app = context; DialogEx* dialog_ex = app->dialog_ex; - dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); - dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); - dialog_ex_set_icon(dialog_ex, 0, 0, NULL); - dialog_ex_set_left_button_text(dialog_ex, NULL); - dialog_ex_set_right_button_text(dialog_ex, NULL); - dialog_ex_set_result_callback(dialog_ex, NULL); - dialog_ex_set_context(dialog_ex, NULL); + dialog_ex_reset(dialog_ex); string_reset(app->text_string); } diff --git a/applications/storage_settings/scenes/storage_settings_scene_sd_info.c b/applications/storage_settings/scenes/storage_settings_scene_sd_info.c index 297041a5..b64caeb2 100644 --- a/applications/storage_settings/scenes/storage_settings_scene_sd_info.c +++ b/applications/storage_settings/scenes/storage_settings_scene_sd_info.c @@ -15,7 +15,6 @@ void storage_settings_scene_sd_info_on_enter(void* context) { dialog_ex_set_context(dialog_ex, app); dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_sd_info_dialog_callback); - dialog_ex_set_left_button_text(dialog_ex, "Back"); if(sd_status != FSE_OK) { dialog_ex_set_header(dialog_ex, "SD card not mounted", 64, 10, AlignCenter, AlignCenter); dialog_ex_set_text( @@ -62,13 +61,7 @@ void storage_settings_scene_sd_info_on_exit(void* context) { StorageSettings* app = context; DialogEx* dialog_ex = app->dialog_ex; - dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); - dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); - dialog_ex_set_icon(dialog_ex, 0, 0, NULL); - dialog_ex_set_left_button_text(dialog_ex, NULL); - dialog_ex_set_right_button_text(dialog_ex, NULL); - dialog_ex_set_result_callback(dialog_ex, NULL); - dialog_ex_set_context(dialog_ex, NULL); + dialog_ex_reset(dialog_ex); string_reset(app->text_string); } diff --git a/applications/storage_settings/scenes/storage_settings_scene_eject_confirm.c b/applications/storage_settings/scenes/storage_settings_scene_unmount_confirm.c similarity index 82% rename from applications/storage_settings/scenes/storage_settings_scene_eject_confirm.c rename to applications/storage_settings/scenes/storage_settings_scene_unmount_confirm.c index 8fc6c871..f9499322 100644 --- a/applications/storage_settings/scenes/storage_settings_scene_eject_confirm.c +++ b/applications/storage_settings/scenes/storage_settings_scene_unmount_confirm.c @@ -11,9 +11,9 @@ void storage_settings_scene_unmount_confirm_on_enter(void* context) { StorageSettings* app = context; FS_Error sd_status = storage_sd_status(app->fs_api); DialogEx* dialog_ex = app->dialog_ex; - dialog_ex_set_left_button_text(dialog_ex, "Back"); if(sd_status == FSE_NOT_READY) { + dialog_ex_set_center_button_text(dialog_ex, "OK"); dialog_ex_set_header(dialog_ex, "SD card not mounted", 64, 10, AlignCenter, AlignCenter); dialog_ex_set_text( dialog_ex, @@ -23,6 +23,7 @@ void storage_settings_scene_unmount_confirm_on_enter(void* context) { AlignCenter, AlignCenter); } else { + dialog_ex_set_left_button_text(dialog_ex, "Cancel"); dialog_ex_set_right_button_text(dialog_ex, "Unmount"); dialog_ex_set_header(dialog_ex, "Unmount SD card?", 64, 10, AlignCenter, AlignCenter); dialog_ex_set_text( @@ -42,6 +43,9 @@ bool storage_settings_scene_unmount_confirm_on_event(void* context, SceneManager if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { + case DialogExResultCenter: + consumed = scene_manager_previous_scene(app->scene_manager); + break; case DialogExResultLeft: consumed = scene_manager_previous_scene(app->scene_manager); break; @@ -50,7 +54,10 @@ bool storage_settings_scene_unmount_confirm_on_event(void* context, SceneManager consumed = true; break; } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = true; } + return consumed; } @@ -58,11 +65,5 @@ void storage_settings_scene_unmount_confirm_on_exit(void* context) { StorageSettings* app = context; DialogEx* dialog_ex = app->dialog_ex; - dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); - dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); - dialog_ex_set_icon(dialog_ex, 0, 0, NULL); - dialog_ex_set_left_button_text(dialog_ex, NULL); - dialog_ex_set_right_button_text(dialog_ex, NULL); - dialog_ex_set_result_callback(dialog_ex, NULL); - dialog_ex_set_context(dialog_ex, NULL); + dialog_ex_reset(dialog_ex); } diff --git a/applications/storage_settings/scenes/storage_settings_scene_ejected.c b/applications/storage_settings/scenes/storage_settings_scene_unmounted.c similarity index 76% rename from applications/storage_settings/scenes/storage_settings_scene_ejected.c rename to applications/storage_settings/scenes/storage_settings_scene_unmounted.c index 08208f87..ddd70d05 100755 --- a/applications/storage_settings/scenes/storage_settings_scene_ejected.c +++ b/applications/storage_settings/scenes/storage_settings_scene_unmounted.c @@ -12,7 +12,7 @@ void storage_settings_scene_unmounted_on_enter(void* context) { FS_Error error = storage_sd_unmount(app->fs_api); DialogEx* dialog_ex = app->dialog_ex; - dialog_ex_set_left_button_text(dialog_ex, "Back"); + dialog_ex_set_center_button_text(dialog_ex, "OK"); if(error == FSE_OK) { dialog_ex_set_header(dialog_ex, "SD card unmounted", 64, 10, AlignCenter, AlignCenter); @@ -39,14 +39,13 @@ bool storage_settings_scene_unmounted_on_event(void* context, SceneManagerEvent if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case DialogExResultLeft: + case DialogExResultCenter: consumed = scene_manager_search_and_switch_to_previous_scene( app->scene_manager, StorageSettingsStart); break; } } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_search_and_switch_to_previous_scene( - app->scene_manager, StorageSettingsStart); + consumed = true; } return consumed; @@ -56,11 +55,5 @@ void storage_settings_scene_unmounted_on_exit(void* context) { StorageSettings* app = context; DialogEx* dialog_ex = app->dialog_ex; - dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); - dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); - dialog_ex_set_icon(dialog_ex, 0, 0, NULL); - dialog_ex_set_left_button_text(dialog_ex, NULL); - dialog_ex_set_right_button_text(dialog_ex, NULL); - dialog_ex_set_result_callback(dialog_ex, NULL); - dialog_ex_set_context(dialog_ex, NULL); + dialog_ex_reset(dialog_ex); } diff --git a/applications/subghz/helpers/subghz_custom_event.h b/applications/subghz/helpers/subghz_custom_event.h index ad70be7e..a2e5ee3e 100644 --- a/applications/subghz/helpers/subghz_custom_event.h +++ b/applications/subghz/helpers/subghz_custom_event.h @@ -5,6 +5,26 @@ typedef enum { SubGhzCustomEventManagerSet, SubGhzCustomEventManagerSetRAW, + //SubmenuIndex + SubmenuIndexPricenton, + SubmenuIndexNiceFlo12bit, + SubmenuIndexNiceFlo24bit, + SubmenuIndexCAME12bit, + SubmenuIndexCAME24bit, + SubmenuIndexCAMETwee, + SubmenuIndexNeroSketch, + SubmenuIndexNeroRadio, + SubmenuIndexGateTX, + SubmenuIndexDoorHan_315_00, + SubmenuIndexDoorHan_433_92, + SubmenuIndexLinear_300_00, + SubmenuIndexLiftMaster_315_00, + SubmenuIndexLiftMaster_390_00, + SubmenuIndexSecPlus_v2_310_00, + SubmenuIndexSecPlus_v2_315_00, + SubmenuIndexSecPlus_v2_390_00, + + //SubGhzCustomEvent SubGhzCustomEventSceneDeleteSuccess = 100, SubGhzCustomEventSceneDelete, SubGhzCustomEventSceneDeleteRAW, diff --git a/applications/subghz/helpers/subghz_frequency_analyzer_worker.c b/applications/subghz/helpers/subghz_frequency_analyzer_worker.c index c0e1fdba..69e59759 100644 --- a/applications/subghz/helpers/subghz_frequency_analyzer_worker.c +++ b/applications/subghz/helpers/subghz_frequency_analyzer_worker.c @@ -1,24 +1,20 @@ #include "subghz_frequency_analyzer_worker.h" -#include +#include #include -#include "../subghz_i.h" +#define TAG "SubghzFrequencyAnalyzerWorker" + +#define SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD -95.0f static const uint8_t subghz_preset_ook_58khz[][2] = { - {CC1101_FIFOTHR, 0x47}, // The only important bit is ADC_RETENTION, FIFO Tx=33 Rx=32 - {CC1101_MDMCFG4, 0xF5}, // Rx BW filter is 58.035714kHz - {CC1101_TEST2, 0x81}, // FIFOTHR ADC_RETENTION=1 matched value - {CC1101_TEST1, 0x35}, // FIFOTHR ADC_RETENTION=1 matched value + {CC1101_MDMCFG4, 0b11110111}, // Rx BW filter is 58.035714kHz /* End */ {0, 0}, }; static const uint8_t subghz_preset_ook_650khz[][2] = { - {CC1101_FIFOTHR, 0x07}, // The only important bit is ADC_RETENTION - {CC1101_MDMCFG4, 0x17}, // Rx BW filter is 650.000kHz - {CC1101_TEST2, 0x88}, - {CC1101_TEST1, 0x31}, + {CC1101_MDMCFG4, 0b00010111}, // Rx BW filter is 650.000kHz /* End */ {0, 0}, }; @@ -27,7 +23,7 @@ struct SubGhzFrequencyAnalyzerWorker { FuriThread* thread; volatile bool worker_running; - uint8_t count_repet; + uint8_t sample_hold_counter; FrequencyRSSI frequency_rssi_buf; SubGhzSetting* setting; @@ -37,6 +33,16 @@ struct SubGhzFrequencyAnalyzerWorker { void* context; }; +static void subghz_frequency_analyzer_worker_load_registers(const uint8_t data[][2]) { + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + size_t i = 0; + while(data[i][0]) { + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, data[i][0], data[i][1]); + i++; + } + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); +} + // running average with adaptive coefficient static uint32_t subghz_frequency_analyzer_worker_expRunningAverageAdaptive( SubGhzFrequencyAnalyzerWorker* instance, @@ -62,32 +68,75 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { SubGhzFrequencyAnalyzerWorker* instance = context; FrequencyRSSI frequency_rssi = {.frequency = 0, .rssi = 0}; - float rssi; - uint32_t frequency; - uint32_t frequency_start; + float rssi = 0; + uint32_t frequency = 0; + CC1101Status status; //Start CC1101 furi_hal_subghz_reset(); - furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); - furi_hal_subghz_set_frequency(433920000); - furi_hal_subghz_flush_rx(); + + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + cc1101_flush_rx(&furi_hal_spi_bus_handle_subghz); + cc1101_flush_tx(&furi_hal_spi_bus_handle_subghz); + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHW); + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_MDMCFG3, + 0b01111111); // symbol rate + cc1101_write_reg( + &furi_hal_spi_bus_handle_subghz, + CC1101_AGCCTRL2, + 0b00000111); // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAGN_TARGET 42 dB + cc1101_write_reg( + &furi_hal_spi_bus_handle_subghz, + CC1101_AGCCTRL1, + 0b00001000); // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 1000 - Absolute carrier sense threshold disabled + cc1101_write_reg( + &furi_hal_spi_bus_handle_subghz, + CC1101_AGCCTRL0, + 0b00110000); // 00 - No hysteresis, medium asymmetric dead zone, medium gain ; 11 - 64 samples agc; 00 - Normal AGC, 00 - 4dB boundary + + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + furi_hal_subghz_set_path(FuriHalSubGhzPathIsolate); - furi_hal_subghz_rx(); while(instance->worker_running) { osDelay(10); + + float rssi_min = 26.0f; + float rssi_avg = 0; + size_t rssi_avg_samples = 0; + frequency_rssi.rssi = -127.0f; furi_hal_subghz_idle(); - furi_hal_subghz_load_registers(subghz_preset_ook_650khz); + subghz_frequency_analyzer_worker_load_registers(subghz_preset_ook_650khz); + + // First stage: coarse scan for(size_t i = 0; i < subghz_setting_get_frequency_count(instance->setting); i++) { if(furi_hal_subghz_is_frequency_valid( subghz_setting_get_frequency(instance->setting, i))) { - furi_hal_subghz_idle(); - frequency = furi_hal_subghz_set_frequency( + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + cc1101_switch_to_idle(&furi_hal_spi_bus_handle_subghz); + frequency = cc1101_set_frequency( + &furi_hal_spi_bus_handle_subghz, subghz_setting_get_frequency(instance->setting, i)); - furi_hal_subghz_rx(); + + cc1101_calibrate(&furi_hal_spi_bus_handle_subghz); + do { + status = cc1101_get_status(&furi_hal_spi_bus_handle_subghz); + } while(status.STATE != CC1101StateIDLE); + + cc1101_switch_to_rx(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + + // delay will be in range between 1 and 2ms osDelay(3); + rssi = furi_hal_subghz_get_rssi(); + + rssi_avg += rssi; + rssi_avg_samples++; + + if(rssi < rssi_min) rssi_min = rssi; + if(frequency_rssi.rssi < rssi) { frequency_rssi.rssi = rssi; frequency_rssi.frequency = frequency; @@ -95,20 +144,45 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { } } - if(frequency_rssi.rssi > -90.0) { - // -0.5 ... 433.92 ... +0.5 - frequency_start = frequency_rssi.frequency - 250000; - //step 10KHz + FURI_LOG_T( + TAG, + "RSSI: avg %f, max %f at %u, min %f", + (double)(rssi_avg / rssi_avg_samples), + (double)frequency_rssi.rssi, + frequency_rssi.frequency, + (double)rssi_min); + + // Second stage: fine scan + if(frequency_rssi.rssi > SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD) { + FURI_LOG_D(TAG, "~:%u:%f", frequency_rssi.frequency, (double)frequency_rssi.rssi); + frequency_rssi.rssi = -127.0; furi_hal_subghz_idle(); - furi_hal_subghz_load_registers(subghz_preset_ook_58khz); - for(uint32_t i = frequency_start; i < frequency_start + 500000; i += 10000) { + subghz_frequency_analyzer_worker_load_registers(subghz_preset_ook_58khz); + //-0.3 ... 433.92 ... +0.3 step 10KHz + for(uint32_t i = frequency_rssi.frequency - 300000; + i < frequency_rssi.frequency + 300000; + i += 20000) { if(furi_hal_subghz_is_frequency_valid(i)) { - furi_hal_subghz_idle(); - frequency = furi_hal_subghz_set_frequency(i); - furi_hal_subghz_rx(); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + cc1101_switch_to_idle(&furi_hal_spi_bus_handle_subghz); + frequency = cc1101_set_frequency(&furi_hal_spi_bus_handle_subghz, i); + + cc1101_calibrate(&furi_hal_spi_bus_handle_subghz); + do { + status = cc1101_get_status(&furi_hal_spi_bus_handle_subghz); + } while(status.STATE != CC1101StateIDLE); + + cc1101_switch_to_rx(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + + // delay will be in range between 1 and 2ms osDelay(3); + rssi = furi_hal_subghz_get_rssi(); + + FURI_LOG_T(TAG, "#:%u:%f", frequency, (double)rssi); + if(frequency_rssi.rssi < rssi) { frequency_rssi.rssi = rssi; frequency_rssi.frequency = frequency; @@ -117,20 +191,24 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { } } - if(frequency_rssi.rssi > -90.0) { - instance->count_repet = 20; + // Deliver results + if(frequency_rssi.rssi > SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD) { + FURI_LOG_D(TAG, "=:%u:%f", frequency_rssi.frequency, (double)frequency_rssi.rssi); + + instance->sample_hold_counter = 20; if(instance->filVal) { frequency_rssi.frequency = subghz_frequency_analyzer_worker_expRunningAverageAdaptive( instance, frequency_rssi.frequency); } - if(instance->pair_callback) + // Deliver callback + if(instance->pair_callback) { instance->pair_callback( instance->context, frequency_rssi.frequency, frequency_rssi.rssi); - + } } else { - if(instance->count_repet > 0) { - instance->count_repet--; + if(instance->sample_hold_counter > 0) { + instance->sample_hold_counter--; } else { instance->filVal = 0; if(instance->pair_callback) instance->pair_callback(instance->context, 0, 0); @@ -145,7 +223,8 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { return 0; } -SubGhzFrequencyAnalyzerWorker* subghz_frequency_analyzer_worker_alloc() { +SubGhzFrequencyAnalyzerWorker* subghz_frequency_analyzer_worker_alloc(void* context) { + furi_assert(context); SubGhzFrequencyAnalyzerWorker* instance = malloc(sizeof(SubGhzFrequencyAnalyzerWorker)); instance->thread = furi_thread_alloc(); @@ -154,8 +233,8 @@ SubGhzFrequencyAnalyzerWorker* subghz_frequency_analyzer_worker_alloc() { furi_thread_set_context(instance->thread, instance); furi_thread_set_callback(instance->thread, subghz_frequency_analyzer_worker_thread); - instance->setting = subghz_setting_alloc(); - subghz_setting_load(instance->setting, "/ext/subghz/assets/setting_frequency_analyzer_user"); + SubGhz* subghz = context; + instance->setting = subghz->setting; return instance; } @@ -163,7 +242,6 @@ void subghz_frequency_analyzer_worker_free(SubGhzFrequencyAnalyzerWorker* instan furi_assert(instance); furi_thread_free(instance->thread); - subghz_setting_free(instance->setting); free(instance); } diff --git a/applications/subghz/helpers/subghz_frequency_analyzer_worker.h b/applications/subghz/helpers/subghz_frequency_analyzer_worker.h index 93f7caf0..424270a0 100644 --- a/applications/subghz/helpers/subghz_frequency_analyzer_worker.h +++ b/applications/subghz/helpers/subghz_frequency_analyzer_worker.h @@ -1,6 +1,7 @@ #pragma once #include +#include "../subghz_i.h" typedef struct SubGhzFrequencyAnalyzerWorker SubGhzFrequencyAnalyzerWorker; @@ -14,9 +15,10 @@ typedef struct { /** Allocate SubGhzFrequencyAnalyzerWorker * + * @param context SubGhz* context * @return SubGhzFrequencyAnalyzerWorker* */ -SubGhzFrequencyAnalyzerWorker* subghz_frequency_analyzer_worker_alloc(); +SubGhzFrequencyAnalyzerWorker* subghz_frequency_analyzer_worker_alloc(void* context); /** Free SubGhzFrequencyAnalyzerWorker * diff --git a/applications/subghz/helpers/subghz_testing.c b/applications/subghz/helpers/subghz_testing.c index f4cbe4a4..8afa868e 100644 --- a/applications/subghz/helpers/subghz_testing.c +++ b/applications/subghz/helpers/subghz_testing.c @@ -4,6 +4,7 @@ const uint32_t subghz_frequencies_testing[] = { /* 300 - 348 */ 300000000, 304500000, + 310000000, 312025000, 313250000, 313625000, @@ -34,4 +35,4 @@ const uint32_t subghz_frequencies_testing[] = { const uint32_t subghz_frequencies_count_testing = sizeof(subghz_frequencies_testing) / sizeof(uint32_t); -const uint32_t subghz_frequencies_433_92_testing = 12; +const uint32_t subghz_frequencies_433_92_testing = 13; diff --git a/applications/subghz/scenes/subghz_scene_delete.c b/applications/subghz/scenes/subghz_scene_delete.c index 83d6d475..43151de2 100644 --- a/applications/subghz/scenes/subghz_scene_delete.c +++ b/applications/subghz/scenes/subghz_scene_delete.c @@ -49,7 +49,7 @@ bool subghz_scene_delete_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventSceneDelete) { - strncpy(subghz->file_path_tmp, subghz->file_path, SUBGHZ_MAX_LEN_NAME); + string_set(subghz->file_path_tmp, subghz->file_path); if(subghz_delete_file(subghz)) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteSuccess); } else { diff --git a/applications/subghz/scenes/subghz_scene_delete_raw.c b/applications/subghz/scenes/subghz_scene_delete_raw.c index 03f0eb81..a20968d5 100644 --- a/applications/subghz/scenes/subghz_scene_delete_raw.c +++ b/applications/subghz/scenes/subghz_scene_delete_raw.c @@ -24,7 +24,7 @@ void subghz_scene_delete_raw_on_enter(void* context) { char delete_str[SUBGHZ_MAX_LEN_NAME + 16]; string_t file_name; string_init(file_name); - path_extract_filename_no_ext(subghz->file_path, file_name); + path_extract_filename(subghz->file_path, file_name, true); snprintf(delete_str, sizeof(delete_str), "\e#Delete %s?\e#", string_get_cstr(file_name)); string_clear(file_name); @@ -61,7 +61,7 @@ bool subghz_scene_delete_raw_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventSceneDeleteRAW) { - strncpy(subghz->file_path_tmp, subghz->file_path, SUBGHZ_MAX_LEN_NAME); + string_set(subghz->file_path_tmp, subghz->file_path); if(subghz_delete_file(subghz)) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteSuccess); } else { diff --git a/applications/subghz/scenes/subghz_scene_delete_success.c b/applications/subghz/scenes/subghz_scene_delete_success.c index d5b3ec2d..d6e1f8dd 100644 --- a/applications/subghz/scenes/subghz_scene_delete_success.c +++ b/applications/subghz/scenes/subghz_scene_delete_success.c @@ -26,9 +26,15 @@ bool subghz_scene_delete_success_on_event(void* context, SceneManagerEvent event if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventSceneDeleteSuccess) { - if(!scene_manager_search_and_switch_to_previous_scene( - subghz->scene_manager, SubGhzSceneSaved)) { + if(scene_manager_search_and_switch_to_previous_scene( + subghz->scene_manager, SubGhzSceneReadRAW)) { + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReadRAW); + } else if(scene_manager_search_and_switch_to_previous_scene( + subghz->scene_manager, SubGhzSceneSaved)) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaved); + } else { + scene_manager_search_and_switch_to_previous_scene( + subghz->scene_manager, SubGhzSceneStart); } return true; } diff --git a/applications/subghz/scenes/subghz_scene_more_raw.c b/applications/subghz/scenes/subghz_scene_more_raw.c index 54bd0815..a5bade92 100644 --- a/applications/subghz/scenes/subghz_scene_more_raw.c +++ b/applications/subghz/scenes/subghz_scene_more_raw.c @@ -45,7 +45,7 @@ bool subghz_scene_more_raw_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteRAW); return true; } else if(event.event == SubmenuIndexEdit) { - memset(subghz->file_path_tmp, 0, sizeof(subghz->file_path_tmp)); + string_reset(subghz->file_path_tmp); scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneMoreRAW, SubmenuIndexEdit); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); diff --git a/applications/subghz/scenes/subghz_scene_read_raw.c b/applications/subghz/scenes/subghz_scene_read_raw.c index 767f0924..97a07140 100644 --- a/applications/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/subghz/scenes/subghz_scene_read_raw.c @@ -23,7 +23,7 @@ bool subghz_scene_read_raw_update_filename(SubGhz* subghz) { break; } - strncpy(subghz->file_path, string_get_cstr(temp_str), SUBGHZ_MAX_LEN_NAME); + string_set(subghz->file_path, temp_str); ret = true; } while(false); @@ -73,13 +73,13 @@ void subghz_scene_read_raw_on_enter(void* context) { subghz_read_raw_set_status(subghz->subghz_read_raw, SubGhzReadRAWStatusIDLE, ""); break; case SubGhzRxKeyStateRAWLoad: - path_extract_filename_no_ext(subghz->file_path, file_name); + path_extract_filename(subghz->file_path, file_name, true); subghz_read_raw_set_status( subghz->subghz_read_raw, SubGhzReadRAWStatusLoadKeyTX, string_get_cstr(file_name)); subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; break; case SubGhzRxKeyStateRAWSave: - path_extract_filename_no_ext(subghz->file_path, file_name); + path_extract_filename(subghz->file_path, file_name, true); subghz_read_raw_set_status( subghz->subghz_read_raw, SubGhzReadRAWStatusSaveKey, string_get_cstr(file_name)); subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; @@ -167,6 +167,12 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { break; case SubGhzCustomEventViewReadRAWErase: + if(subghz->txrx->rx_key_state == SubGhzRxKeyStateAddKey) { + if(subghz_scene_read_raw_update_filename(subghz)) { + string_set(subghz->file_path_tmp, subghz->file_path); + subghz_delete_file(subghz); + } + } subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; notification_message(subghz->notifications, &sequence_reset_rgb); return true; @@ -180,7 +186,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneMoreRAW); return true; } else { - furi_crash("SugGhz: RAW file name update error."); + furi_crash("SubGhz: RAW file name update error."); } break; @@ -273,7 +279,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { subghz->state_notifications = SubGhzNotificationStateRx; subghz->txrx->rx_key_state = SubGhzRxKeyStateAddKey; } else { - string_set(subghz->error_str, "Function requires\nan SD card."); + string_set_str(subghz->error_str, "Function requires\nan SD card."); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); } } diff --git a/applications/subghz/scenes/subghz_scene_save_name.c b/applications/subghz/scenes/subghz_scene_save_name.c index 617709cc..95fc1938 100644 --- a/applications/subghz/scenes/subghz_scene_save_name.c +++ b/applications/subghz/scenes/subghz_scene_save_name.c @@ -1,4 +1,6 @@ #include "../subghz_i.h" +#include "m-string.h" +#include "subghz/types.h" #include #include "../helpers/subghz_custom_event.h" #include @@ -20,45 +22,49 @@ void subghz_scene_save_name_on_enter(void* context) { bool dev_name_empty = false; string_t file_name; + string_t dir_name; string_init(file_name); + string_init(dir_name); - if(!strcmp(subghz->file_path, "")) { + if(!subghz_path_is_file(subghz->file_path)) { char file_name_buf[SUBGHZ_MAX_LEN_NAME] = {0}; set_random_name(file_name_buf, SUBGHZ_MAX_LEN_NAME); - string_set(file_name, file_name_buf); - strncpy(subghz->file_dir, SUBGHZ_APP_FOLDER, SUBGHZ_MAX_LEN_NAME); + string_set_str(file_name, file_name_buf); + string_set_str(subghz->file_path, SUBGHZ_APP_FOLDER); //highlighting the entire filename by default dev_name_empty = true; } else { - strncpy(subghz->file_path_tmp, subghz->file_path, SUBGHZ_MAX_LEN_NAME); - path_extract_dirname(subghz->file_path, file_name); - strncpy(subghz->file_dir, string_get_cstr(file_name), SUBGHZ_MAX_LEN_NAME); - path_extract_filename_no_ext(subghz->file_path, file_name); + string_set(subghz->file_path_tmp, subghz->file_path); + path_extract_dirname(string_get_cstr(subghz->file_path), dir_name); + path_extract_filename(subghz->file_path, file_name, true); if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != SubGhzCustomEventManagerNoSet) { subghz_get_next_name_file(subghz, SUBGHZ_MAX_LEN_NAME); - path_extract_filename_no_ext(subghz->file_path, file_name); + path_extract_filename(subghz->file_path, file_name, true); if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) == SubGhzCustomEventManagerSetRAW) { dev_name_empty = true; } } + string_set(subghz->file_path, dir_name); } - strncpy(subghz->file_path, string_get_cstr(file_name), SUBGHZ_MAX_LEN_NAME); + + strncpy(subghz->file_name_tmp, string_get_cstr(file_name), SUBGHZ_MAX_LEN_NAME); text_input_set_header_text(text_input, "Name signal"); text_input_set_result_callback( text_input, subghz_scene_save_name_text_input_callback, subghz, - subghz->file_path, + subghz->file_name_tmp, MAX_TEXT_INPUT_LEN, // buffer size dev_name_empty); - ValidatorIsFile* validator_is_file = - validator_is_file_alloc_init(subghz->file_dir, SUBGHZ_APP_EXTENSION, NULL); + ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( + string_get_cstr(subghz->file_path), SUBGHZ_APP_EXTENSION, NULL); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); string_clear(file_name); + string_clear(dir_name); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdTextInput); } @@ -66,18 +72,15 @@ void subghz_scene_save_name_on_enter(void* context) { bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeBack) { - strncpy(subghz->file_path, subghz->file_path_tmp, SUBGHZ_MAX_LEN_NAME); + string_set(subghz->file_path, subghz->file_path_tmp); scene_manager_previous_scene(subghz->scene_manager); return true; } else if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventSceneSaveName) { - if(strcmp(subghz->file_path, "")) { - string_t temp_str; - string_init_printf( - temp_str, "%s/%s%s", subghz->file_dir, subghz->file_path, SUBGHZ_APP_EXTENSION); - strncpy(subghz->file_path, string_get_cstr(temp_str), SUBGHZ_MAX_LEN_NAME); - string_clear(temp_str); - if(strcmp(subghz->file_path_tmp, "")) { + if(strcmp(subghz->file_name_tmp, "")) { + string_cat_printf( + subghz->file_path, "/%s%s", subghz->file_name_tmp, SUBGHZ_APP_EXTENSION); + if(subghz_path_is_file(subghz->file_path_tmp)) { if(!subghz_rename_file(subghz)) { return false; } @@ -85,7 +88,7 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSetType) != SubGhzCustomEventManagerNoSet) { subghz_save_protocol_to_file( - subghz, subghz->txrx->fff_data, subghz->file_path); + subghz, subghz->txrx->fff_data, string_get_cstr(subghz->file_path)); scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneSetType, @@ -95,13 +98,14 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { subghz, subghz_history_get_raw_data( subghz->txrx->history, subghz->txrx->idx_menu_chosen), - subghz->file_path); + string_get_cstr(subghz->file_path)); } } if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != SubGhzCustomEventManagerNoSet) { - subghz_protocol_raw_gen_fff_data(subghz->txrx->fff_data, subghz->file_path); + subghz_protocol_raw_gen_fff_data( + subghz->txrx->fff_data, string_get_cstr(subghz->file_path)); scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerNoSet); } else { @@ -111,7 +115,7 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveSuccess); return true; } else { - string_set(subghz->error_str, "No name file"); + string_set_str(subghz->error_str, "No name file"); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); return true; } diff --git a/applications/subghz/scenes/subghz_scene_set_type.c b/applications/subghz/scenes/subghz_scene_set_type.c index 9dad8cd9..a7a4491e 100644 --- a/applications/subghz/scenes/subghz_scene_set_type.c +++ b/applications/subghz/scenes/subghz_scene_set_type.c @@ -1,5 +1,7 @@ #include "../subghz_i.h" #include +#include +#include #include #include #include @@ -8,21 +10,6 @@ #define TAG "SubGhzSetType" -enum SubmenuIndex { - SubmenuIndexPricenton, - SubmenuIndexNiceFlo12bit, - SubmenuIndexNiceFlo24bit, - SubmenuIndexCAME12bit, - SubmenuIndexCAME24bit, - SubmenuIndexCAMETwee, - SubmenuIndexNeroSketch, - SubmenuIndexNeroRadio, - SubmenuIndexGateTX, - SubmenuIndexDoorHan_315_00, - SubmenuIndexDoorHan_433_92, - SubmenuIndexFirefly_300_00, -}; - bool subghz_scene_set_type_submenu_gen_data_protocol( void* context, const char* protocol_name, @@ -39,7 +26,7 @@ bool subghz_scene_set_type_submenu_gen_data_protocol( subghz_receiver_search_decoder_base_by_name(subghz->txrx->receiver, protocol_name); if(subghz->txrx->decoder_result == NULL) { - string_set(subghz->error_str, "Protocol not found"); + string_set_str(subghz->error_str, "Protocol not found"); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); return false; } @@ -110,8 +97,8 @@ void subghz_scene_set_type_on_enter(void* context) { subghz); submenu_add_item( subghz->submenu, - "Firefly_300", - SubmenuIndexFirefly_300_00, + "Linear_300", + SubmenuIndexLinear_300_00, subghz_scene_set_type_submenu_callback, subghz); submenu_add_item( @@ -142,6 +129,36 @@ void subghz_scene_set_type_on_enter(void* context) { SubmenuIndexDoorHan_433_92, subghz_scene_set_type_submenu_callback, subghz); + submenu_add_item( + subghz->submenu, + "LiftMaster_315", + SubmenuIndexLiftMaster_315_00, + subghz_scene_set_type_submenu_callback, + subghz); + submenu_add_item( + subghz->submenu, + "LiftMaster_390", + SubmenuIndexLiftMaster_390_00, + subghz_scene_set_type_submenu_callback, + subghz); + submenu_add_item( + subghz->submenu, + "Security+2.0_310", + SubmenuIndexSecPlus_v2_310_00, + subghz_scene_set_type_submenu_callback, + subghz); + submenu_add_item( + subghz->submenu, + "Security+2.0_315", + SubmenuIndexSecPlus_v2_315_00, + subghz_scene_set_type_submenu_callback, + subghz); + submenu_add_item( + subghz->submenu, + "Security+2.0_390", + SubmenuIndexSecPlus_v2_390_00, + subghz_scene_set_type_submenu_callback, + subghz); submenu_set_selected_item( subghz->submenu, scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSetType)); @@ -219,11 +236,11 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { generated_protocol = true; } break; - case SubmenuIndexFirefly_300_00: + case SubmenuIndexLinear_300_00: key = (key & 0x3FF); if(subghz_scene_set_type_submenu_gen_data_protocol( subghz, - SUBGHZ_PROTOCOL_FIREFLY_NAME, + SUBGHZ_PROTOCOL_LINEAR_NAME, key, 10, 300000000, @@ -282,7 +299,7 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { } subghz_transmitter_free(subghz->txrx->transmitter); if(!generated_protocol) { - string_set( + string_set_str( subghz->error_str, "Function requires\nan SD card with\nfresh databases."); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); } @@ -306,21 +323,103 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { } subghz_transmitter_free(subghz->txrx->transmitter); if(!generated_protocol) { - string_set( + string_set_str( subghz->error_str, "Function requires\nan SD card with\nfresh databases."); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); } break; + case SubmenuIndexLiftMaster_315_00: + while(!subghz_protocol_secplus_v1_check_fixed(key)) { + key = subghz_random_serial(); + } + if(subghz_scene_set_type_submenu_gen_data_protocol( + subghz, + SUBGHZ_PROTOCOL_SECPLUS_V1_NAME, + (uint64_t)key << 32 | 0xE6000000, + 42, + 315000000, + FuriHalSubGhzPresetOok650Async)) { + generated_protocol = true; + } + break; + case SubmenuIndexLiftMaster_390_00: + while(!subghz_protocol_secplus_v1_check_fixed(key)) { + key = subghz_random_serial(); + } + if(subghz_scene_set_type_submenu_gen_data_protocol( + subghz, + SUBGHZ_PROTOCOL_SECPLUS_V1_NAME, + (uint64_t)key << 32 | 0xE6000000, + 42, + 390000000, + FuriHalSubGhzPresetOok650Async)) { + generated_protocol = true; + } + break; + case SubmenuIndexSecPlus_v2_310_00: + subghz->txrx->transmitter = subghz_transmitter_alloc_init( + subghz->txrx->environment, SUBGHZ_PROTOCOL_SECPLUS_V2_NAME); + if(subghz->txrx->transmitter) { + subghz_protocol_secplus_v2_create_data( + subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter), + subghz->txrx->fff_data, + key, + 0x68, + 0xE500000, + 310000000, + FuriHalSubGhzPresetOok650Async); + generated_protocol = true; + } else { + generated_protocol = false; + } + subghz_transmitter_free(subghz->txrx->transmitter); + break; + case SubmenuIndexSecPlus_v2_315_00: + subghz->txrx->transmitter = subghz_transmitter_alloc_init( + subghz->txrx->environment, SUBGHZ_PROTOCOL_SECPLUS_V2_NAME); + if(subghz->txrx->transmitter) { + subghz_protocol_secplus_v2_create_data( + subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter), + subghz->txrx->fff_data, + key, + 0x68, + 0xE500000, + 315000000, + FuriHalSubGhzPresetOok650Async); + generated_protocol = true; + } else { + generated_protocol = false; + } + subghz_transmitter_free(subghz->txrx->transmitter); + break; + case SubmenuIndexSecPlus_v2_390_00: + subghz->txrx->transmitter = subghz_transmitter_alloc_init( + subghz->txrx->environment, SUBGHZ_PROTOCOL_SECPLUS_V2_NAME); + if(subghz->txrx->transmitter) { + subghz_protocol_secplus_v2_create_data( + subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter), + subghz->txrx->fff_data, + key, + 0x68, + 0xE500000, + 390000000, + FuriHalSubGhzPresetOok650Async); + generated_protocol = true; + } else { + generated_protocol = false; + } + subghz_transmitter_free(subghz->txrx->transmitter); + break; default: return false; break; } + scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneSetType, event.event); + if(generated_protocol) { subghz_file_name_clear(subghz); DOLPHIN_DEED(DolphinDeedSubGhzAddManually); - scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneSetType, SubGhzCustomEventManagerSet); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); return true; } diff --git a/applications/subghz/scenes/subghz_scene_show_error.c b/applications/subghz/scenes/subghz_scene_show_error.c index 2998bf8e..5632a859 100644 --- a/applications/subghz/scenes/subghz_scene_show_error.c +++ b/applications/subghz/scenes/subghz_scene_show_error.c @@ -37,7 +37,7 @@ void subghz_scene_show_error_on_enter(void* context) { if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneShowError) == SubGhzCustomEventManagerSet) { widget_add_button_element( - subghz->widget, GuiButtonTypeRight, "Ok", subghz_scene_show_error_callback, subghz); + subghz->widget, GuiButtonTypeRight, "OK", subghz_scene_show_error_callback, subghz); } else { notification_message(subghz->notifications, &subghs_sequence_sd_error); } diff --git a/applications/subghz/scenes/subghz_scene_transmitter.c b/applications/subghz/scenes/subghz_scene_transmitter.c index a7244424..b8b22749 100644 --- a/applications/subghz/scenes/subghz_scene_transmitter.c +++ b/applications/subghz/scenes/subghz_scene_transmitter.c @@ -94,7 +94,7 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { subghz->scene_manager, SubGhzSceneStart); return true; } else if(event.event == SubGhzCustomEventViewTransmitterError) { - string_set(subghz->error_str, "Protocol not found"); + string_set_str(subghz->error_str, "Protocol not found"); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); } } else if(event.type == SceneManagerEventTypeTick) { @@ -108,8 +108,5 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { void subghz_scene_transmitter_on_exit(void* context) { SubGhz* subghz = context; - //Restore default setting - subghz->txrx->frequency = subghz_setting_get_default_frequency(subghz->setting); - subghz->txrx->preset = FuriHalSubGhzPresetOok650Async; subghz->state_notifications = SubGhzNotificationStateIDLE; } diff --git a/applications/subghz/subghz.c b/applications/subghz/subghz.c index f597adde..d9dc19f4 100644 --- a/applications/subghz/subghz.c +++ b/applications/subghz/subghz.c @@ -1,5 +1,7 @@ /* Abandon hope, all ye who enter here. */ +#include "m-string.h" +#include "subghz/types.h" #include "subghz_i.h" #include @@ -24,6 +26,9 @@ void subghz_tick_event_callback(void* context) { SubGhz* subghz_alloc() { SubGhz* subghz = malloc(sizeof(SubGhz)); + string_init(subghz->file_path); + string_init(subghz->file_path_tmp); + // GUI subghz->gui = furi_record_open("gui"); @@ -241,9 +246,9 @@ void subghz_free(SubGhz* subghz) { furi_record_close("notification"); subghz->notifications = NULL; - // About birds - furi_assert(subghz->file_path[SUBGHZ_MAX_LEN_NAME] == 0); - furi_assert(subghz->file_path_tmp[SUBGHZ_MAX_LEN_NAME] == 0); + // Path strings + string_clear(subghz->file_path); + string_clear(subghz->file_path_tmp); // The rest free(subghz); @@ -260,7 +265,7 @@ int32_t subghz_app(void* p) { // Check argument and run corresponding scene if(p) { if(subghz_key_load(subghz, p)) { - strncpy(subghz->file_path, p, SUBGHZ_MAX_LEN_NAME); + string_set_str(subghz->file_path, p); if((!strcmp(subghz->txrx->decoder_result->protocol->name, "RAW"))) { //Load Raw TX @@ -276,12 +281,13 @@ int32_t subghz_app(void* p) { view_dispatcher_stop(subghz->view_dispatcher); } } else { + string_set_str(subghz->file_path, SUBGHZ_APP_FOLDER); if(load_database) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneStart); } else { scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneShowError, SubGhzCustomEventManagerSet); - string_set( + string_set_str( subghz->error_str, "No SD card or\ndatabase found.\nSome app function\nmay be reduced."); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); diff --git a/applications/subghz/subghz_cli.c b/applications/subghz/subghz_cli.c index f919e1cf..6c92a5d6 100644 --- a/applications/subghz/subghz_cli.c +++ b/applications/subghz/subghz_cli.c @@ -303,7 +303,7 @@ void subghz_cli_command_decode_raw(Cli* cli, string_t args, void* context) { UNUSED(context); string_t file_name; string_init(file_name); - string_set(file_name, "/any/subghz/test.sub"); + string_set_str(file_name, "/any/subghz/test.sub"); Storage* storage = furi_record_open("storage"); FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); diff --git a/applications/subghz/subghz_history.c b/applications/subghz/subghz_history.c index cb30726b..a8f86eec 100644 --- a/applications/subghz/subghz_history.c +++ b/applications/subghz/subghz_history.c @@ -169,14 +169,14 @@ bool subghz_history_add_to_history( break; } if(!strcmp(string_get_cstr(instance->tmp_string), "KeeLoq")) { - string_set(instance->tmp_string, "KL "); + string_set_str(instance->tmp_string, "KL "); if(!flipper_format_read_string(item->flipper_string, "Manufacture", text)) { FURI_LOG_E(TAG, "Missing Protocol"); break; } string_cat(instance->tmp_string, text); } else if(!strcmp(string_get_cstr(instance->tmp_string), "Star Line")) { - string_set(instance->tmp_string, "SL "); + string_set_str(instance->tmp_string, "SL "); if(!flipper_format_read_string(item->flipper_string, "Manufacture", text)) { FURI_LOG_E(TAG, "Missing Protocol"); break; diff --git a/applications/subghz/subghz_i.c b/applications/subghz/subghz_i.c index ff22b429..33ceb9bb 100644 --- a/applications/subghz/subghz_i.c +++ b/applications/subghz/subghz_i.c @@ -1,5 +1,8 @@ #include "subghz_i.h" +#include "assets_icons.h" +#include "m-string.h" +#include "subghz/types.h" #include #include #include @@ -45,13 +48,13 @@ void subghz_get_frequency_modulation(SubGhz* subghz, string_t frequency, string_ if(modulation != NULL) { if(subghz->txrx->preset == FuriHalSubGhzPresetOok650Async || subghz->txrx->preset == FuriHalSubGhzPresetOok270Async) { - string_set(modulation, "AM"); + string_set_str(modulation, "AM"); } else if( subghz->txrx->preset == FuriHalSubGhzPreset2FSKDev238Async || subghz->txrx->preset == FuriHalSubGhzPreset2FSKDev476Async) { - string_set(modulation, "FM"); + string_set_str(modulation, "FM"); } else { - furi_crash("SugGhz: Modulation is incorrect."); + furi_crash("SubGhz: Modulation is incorrect."); } } } @@ -68,7 +71,7 @@ void subghz_begin(SubGhz* subghz, FuriHalSubGhzPreset preset) { uint32_t subghz_rx(SubGhz* subghz, uint32_t frequency) { furi_assert(subghz); if(!furi_hal_subghz_is_frequency_valid(frequency)) { - furi_crash("SugGhz: Incorrect RX frequency."); + furi_crash("SubGhz: Incorrect RX frequency."); } furi_assert( subghz->txrx->txrx_state != SubGhzTxRxStateRx && @@ -89,7 +92,7 @@ uint32_t subghz_rx(SubGhz* subghz, uint32_t frequency) { static bool subghz_tx(SubGhz* subghz, uint32_t frequency) { furi_assert(subghz); if(!furi_hal_subghz_is_frequency_valid(frequency)) { - furi_crash("SugGhz: Incorrect TX frequency."); + furi_crash("SubGhz: Incorrect TX frequency."); } furi_assert(subghz->txrx->txrx_state != SubGhzTxRxStateSleep); furi_hal_subghz_idle(); @@ -171,7 +174,9 @@ bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format) { } if(!ret) { subghz_transmitter_free(subghz->txrx->transmitter); - subghz_idle(subghz); + if(subghz->txrx->txrx_state != SubGhzTxRxStateSleep) { + subghz_idle(subghz); + } } } while(false); @@ -189,8 +194,9 @@ void subghz_tx_stop(SubGhz* subghz) { //if protocol dynamic then we save the last upload if((subghz->txrx->decoder_result->protocol->type == SubGhzProtocolTypeDynamic) && - (strcmp(subghz->file_path, ""))) { - subghz_save_protocol_to_file(subghz, subghz->txrx->fff_data, subghz->file_path); + (subghz_path_is_file(subghz->file_path))) { + subghz_save_protocol_to_file( + subghz, subghz->txrx->fff_data, string_get_cstr(subghz->file_path)); } subghz_idle(subghz); notification_message(subghz->notifications, &sequence_reset_red); @@ -332,10 +338,10 @@ bool subghz_get_next_name_file(SubGhz* subghz, uint8_t max_len) { bool res = false; - if(strcmp(subghz->file_path, "")) { + if(subghz_path_is_file(subghz->file_path)) { //get the name of the next free file - path_extract_filename_no_ext(subghz->file_path, file_name); - path_extract_dirname(subghz->file_path, file_path); + path_extract_filename(subghz->file_path, file_name, true); + path_extract_dirname(string_get_cstr(subghz->file_path), file_path); storage_get_next_filename( storage, @@ -351,7 +357,7 @@ bool subghz_get_next_name_file(SubGhz* subghz, uint8_t max_len) { string_get_cstr(file_path), string_get_cstr(file_name), SUBGHZ_APP_EXTENSION); - strncpy(subghz->file_path, string_get_cstr(temp_str), SUBGHZ_MAX_LEN_NAME); + string_set(subghz->file_path, temp_str); res = true; } @@ -411,19 +417,17 @@ bool subghz_load_protocol_from_file(SubGhz* subghz) { string_init(file_path); // Input events and views are managed by file_select - bool res = dialog_file_select_show( + bool res = dialog_file_browser_show( subghz->dialogs, - SUBGHZ_APP_FOLDER, - SUBGHZ_APP_EXTENSION, subghz->file_path, - sizeof(subghz->file_path), - NULL); + subghz->file_path, + SUBGHZ_APP_EXTENSION, + true, + &I_sub1_10px, + true); if(res) { - string_printf( - file_path, "%s/%s%s", SUBGHZ_APP_FOLDER, subghz->file_path, SUBGHZ_APP_EXTENSION); - strncpy(subghz->file_path, string_get_cstr(file_path), SUBGHZ_MAX_LEN_NAME); - res = subghz_key_load(subghz, subghz->file_path); + res = subghz_key_load(subghz, string_get_cstr(subghz->file_path)); } string_clear(file_path); @@ -437,9 +441,9 @@ bool subghz_rename_file(SubGhz* subghz) { Storage* storage = furi_record_open("storage"); - if(strcmp(subghz->file_path_tmp, subghz->file_path)) { - FS_Error fs_result = - storage_common_rename(storage, subghz->file_path_tmp, subghz->file_path); + if(string_cmp(subghz->file_path_tmp, subghz->file_path)) { + FS_Error fs_result = storage_common_rename( + storage, string_get_cstr(subghz->file_path_tmp), string_get_cstr(subghz->file_path)); if(fs_result != FSE_OK) { dialog_message_show_storage_error(subghz->dialogs, "Cannot rename\n file/directory"); @@ -455,7 +459,7 @@ bool subghz_delete_file(SubGhz* subghz) { furi_assert(subghz); Storage* storage = furi_record_open("storage"); - bool result = storage_simply_remove(storage, subghz->file_path_tmp); + bool result = storage_simply_remove(storage, string_get_cstr(subghz->file_path_tmp)); furi_record_close("storage"); subghz_file_name_clear(subghz); @@ -465,8 +469,12 @@ bool subghz_delete_file(SubGhz* subghz) { void subghz_file_name_clear(SubGhz* subghz) { furi_assert(subghz); - memset(subghz->file_path, 0, sizeof(subghz->file_path)); - memset(subghz->file_path_tmp, 0, sizeof(subghz->file_path_tmp)); + string_set_str(subghz->file_path, SUBGHZ_APP_FOLDER); + string_reset(subghz->file_path_tmp); +} + +bool subghz_path_is_file(string_t path) { + return string_end_with_str_p(path, SUBGHZ_APP_EXTENSION); } uint32_t subghz_random_serial(void) { diff --git a/applications/subghz/subghz_i.h b/applications/subghz/subghz_i.h index 1e917af5..5d1b0699 100644 --- a/applications/subghz/subghz_i.h +++ b/applications/subghz/subghz_i.h @@ -35,7 +35,7 @@ #include #include -#define SUBGHZ_MAX_LEN_NAME 250 +#define SUBGHZ_MAX_LEN_NAME 64 /** SubGhzNotification state */ typedef enum { @@ -119,10 +119,9 @@ struct SubGhz { TextInput* text_input; Widget* widget; DialogsApp* dialogs; - char file_path[SUBGHZ_MAX_LEN_NAME + 1]; - char file_path_tmp[SUBGHZ_MAX_LEN_NAME + 1]; - //ToDo you can get rid of it, you need to refactor text input to return the path to the folder - char file_dir[SUBGHZ_MAX_LEN_NAME + 1]; + string_t file_path; + string_t file_path_tmp; + char file_name_tmp[SUBGHZ_MAX_LEN_NAME]; SubGhzNotificationState state_notifications; SubGhzViewReceiver* subghz_receiver; @@ -173,5 +172,6 @@ bool subghz_load_protocol_from_file(SubGhz* subghz); bool subghz_rename_file(SubGhz* subghz); bool subghz_delete_file(SubGhz* subghz); void subghz_file_name_clear(SubGhz* subghz); +bool subghz_path_is_file(string_t path); uint32_t subghz_random_serial(void); void subghz_hopper_update(SubGhz* subghz); diff --git a/applications/subghz/subghz_setting.c b/applications/subghz/subghz_setting.c index 4d40d49e..9dcfb291 100644 --- a/applications/subghz/subghz_setting.c +++ b/applications/subghz/subghz_setting.c @@ -7,20 +7,19 @@ #define TAG "SubGhzSetting" -#define SUBGHZ_SETTING_FILE_VERSION 1 #define SUBGHZ_SETTING_FILE_TYPE "Flipper SubGhz Setting File" +#define SUBGHZ_SETTING_FILE_VERSION 1 -typedef enum { - SubGhzSettingStateNoLoad = 0, - SubGhzSettingStateLoadFrequencyDefault, - SubGhzSettingStateOkLoad, -} SubGhzSettingState; +#define FREQUENCY_FLAG_DEFAULT (1 << 31) +#define FREQUENCY_MASK (0xFFFFFFFF ^ FREQUENCY_FLAG_DEFAULT) -static const uint32_t subghz_frequencies[] = { +/* Default */ +static const uint32_t subghz_frequency_list[] = { /* 300 - 348 */ 300000000, 303875000, 304250000, + 310000000, 315000000, 318000000, @@ -29,7 +28,7 @@ static const uint32_t subghz_frequencies[] = { 418000000, 433075000, /* LPD433 first */ 433420000, - 433920000, /* LPD433 mid */ + 433920000 | FREQUENCY_FLAG_DEFAULT, /* LPD433 mid */ 434420000, 434775000, /* LPD433 last channels */ 438900000, @@ -40,7 +39,9 @@ static const uint32_t subghz_frequencies[] = { 925000000, 0, }; -static const uint32_t subghz_hopper_frequencies[] = { + +static const uint32_t subghz_hopper_frequency_list[] = { + 310000000, 315000000, 318000000, 390000000, @@ -48,13 +49,14 @@ static const uint32_t subghz_hopper_frequencies[] = { 868350000, 0, }; -static const uint32_t subghz_frequency_default_index = 9; -static const uint32_t subghz_frequencies_region_eu_ru[] = { +/* Europe and Russia */ +static const uint32_t subghz_frequency_list_region_eu_ru[] = { /* 300 - 348 */ 300000000, 303875000, 304250000, + 310000000, 315000000, 318000000, @@ -63,7 +65,7 @@ static const uint32_t subghz_frequencies_region_eu_ru[] = { 418000000, 433075000, /* LPD433 first */ 433420000, - 433920000, /* LPD433 mid */ + 433920000 | FREQUENCY_FLAG_DEFAULT, /* LPD433 mid */ 434420000, 434775000, /* LPD433 last channels */ 438900000, @@ -74,7 +76,8 @@ static const uint32_t subghz_frequencies_region_eu_ru[] = { 925000000, 0, }; -static const uint32_t subghz_hopper_frequencies_region_eu_ru[] = { +static const uint32_t subghz_hopper_frequency_list_region_eu_ru[] = { + 310000000, 315000000, 318000000, 390000000, @@ -82,13 +85,14 @@ static const uint32_t subghz_hopper_frequencies_region_eu_ru[] = { 868350000, 0, }; -static const uint32_t subghz_frequency_default_index_region_eu_ru = 9; -static const uint32_t subghz_frequencies_region_us_ca_au[] = { +/* Region 0 */ +static const uint32_t subghz_frequency_list_region_us_ca_au[] = { /* 300 - 348 */ 300000000, 303875000, 304250000, + 310000000, 315000000, 318000000, @@ -97,7 +101,7 @@ static const uint32_t subghz_frequencies_region_us_ca_au[] = { 418000000, 433075000, /* LPD433 first */ 433420000, - 433920000, /* LPD433 mid */ + 433920000 | FREQUENCY_FLAG_DEFAULT, /* LPD433 mid */ 434420000, 434775000, /* LPD433 last channels */ 438900000, @@ -108,7 +112,8 @@ static const uint32_t subghz_frequencies_region_us_ca_au[] = { 925000000, 0, }; -static const uint32_t subghz_hopper_frequencies_region_us_ca_au[] = { +static const uint32_t subghz_hopper_frequency_list_region_us_ca_au[] = { + 310000000, 315000000, 318000000, 390000000, @@ -116,13 +121,13 @@ static const uint32_t subghz_hopper_frequencies_region_us_ca_au[] = { 868350000, 0, }; -static const uint32_t subghz_frequency_default_index_region_us_ca_au = 9; -static const uint32_t subghz_frequencies_region_jp[] = { +static const uint32_t subghz_frequency_list_region_jp[] = { /* 300 - 348 */ 300000000, 303875000, 304250000, + 310000000, 315000000, 318000000, @@ -131,7 +136,7 @@ static const uint32_t subghz_frequencies_region_jp[] = { 418000000, 433075000, /* LPD433 first */ 433420000, - 433920000, /* LPD433 mid */ + 433920000 | FREQUENCY_FLAG_DEFAULT, /* LPD433 mid */ 434420000, 434775000, /* LPD433 last channels */ 438900000, @@ -142,7 +147,8 @@ static const uint32_t subghz_frequencies_region_jp[] = { 925000000, 0, }; -static const uint32_t subghz_hopper_frequencies_region_jp[] = { +static const uint32_t subghz_hopper_frequency_list_region_jp[] = { + 310000000, 315000000, 318000000, 390000000, @@ -150,72 +156,88 @@ static const uint32_t subghz_hopper_frequencies_region_jp[] = { 868350000, 0, }; -static const uint32_t subghz_frequency_default_index_region_jp = 9; -LIST_DEF(FrequenciesList, uint32_t) +LIST_DEF(FrequencyList, uint32_t) + +#define M_OPL_FrequencyList_t() LIST_OPLIST(FrequencyList) struct SubGhzSetting { - FrequenciesList_t frequencies; - FrequenciesList_t hopper_frequencies; - size_t frequencies_count; - size_t hopper_frequencies_count; - uint32_t frequency_default_index; + FrequencyList_t frequencies; + FrequencyList_t hopper_frequencies; }; SubGhzSetting* subghz_setting_alloc(void) { SubGhzSetting* instance = malloc(sizeof(SubGhzSetting)); - FrequenciesList_init(instance->frequencies); - FrequenciesList_init(instance->hopper_frequencies); + FrequencyList_init(instance->frequencies); + FrequencyList_init(instance->hopper_frequencies); return instance; } void subghz_setting_free(SubGhzSetting* instance) { furi_assert(instance); - FrequenciesList_clear(instance->frequencies); - FrequenciesList_clear(instance->hopper_frequencies); + FrequencyList_clear(instance->frequencies); + FrequencyList_clear(instance->hopper_frequencies); free(instance); } -void subghz_setting_load_default( +static void subghz_setting_load_default_region( SubGhzSetting* instance, const uint32_t frequencies[], - const uint32_t hopper_frequencies[], - const uint32_t frequency_default_index) { + const uint32_t hopper_frequencies[]) { furi_assert(instance); - size_t i = 0; - FrequenciesList_clear(instance->frequencies); - FrequenciesList_clear(instance->hopper_frequencies); - i = 0; - while(frequencies[i]) { - FrequenciesList_push_back(instance->frequencies, frequencies[i]); - i++; - } - instance->frequencies_count = i; - i = 0; - while(hopper_frequencies[i]) { - FrequenciesList_push_back(instance->hopper_frequencies, hopper_frequencies[i]); - i++; - } - instance->hopper_frequencies_count = i; + FrequencyList_reset(instance->frequencies); + FrequencyList_reset(instance->hopper_frequencies); - instance->frequency_default_index = frequency_default_index; + while(*frequencies) { + FrequencyList_push_back(instance->frequencies, *frequencies); + frequencies++; + } + + while(*hopper_frequencies) { + FrequencyList_push_back(instance->hopper_frequencies, *hopper_frequencies); + hopper_frequencies++; + } +} + +void subghz_setting_load_default(SubGhzSetting* instance) { + switch(furi_hal_version_get_hw_region()) { + case FuriHalVersionRegionEuRu: + subghz_setting_load_default_region( + instance, + subghz_frequency_list_region_eu_ru, + subghz_hopper_frequency_list_region_eu_ru); + break; + case FuriHalVersionRegionUsCaAu: + subghz_setting_load_default_region( + instance, + subghz_frequency_list_region_us_ca_au, + subghz_hopper_frequency_list_region_us_ca_au); + break; + case FuriHalVersionRegionJp: + subghz_setting_load_default_region( + instance, subghz_frequency_list_region_jp, subghz_hopper_frequency_list_region_jp); + break; + + default: + subghz_setting_load_default_region( + instance, subghz_frequency_list, subghz_hopper_frequency_list); + break; + } } void subghz_setting_load(SubGhzSetting* instance, const char* file_path) { furi_assert(instance); - FrequenciesList_clear(instance->frequencies); - FrequenciesList_clear(instance->hopper_frequencies); - Storage* storage = furi_record_open("storage"); FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); string_t temp_str; string_init(temp_str); uint32_t temp_data32; - SubGhzSettingState loading = SubGhzSettingStateNoLoad; - uint16_t i = 0; + bool temp_bool; + + subghz_setting_load_default(instance); if(file_path) { do { @@ -236,63 +258,60 @@ void subghz_setting_load(SubGhzSetting* instance, const char* file_path) { break; } + // Standard frequencies (optional) + temp_bool = true; + flipper_format_read_bool(fff_data_file, "add_standard_frequencies", &temp_bool, 1); + if(!temp_bool) { + FURI_LOG_I(TAG, "Removing standard frequencies"); + FrequencyList_reset(instance->frequencies); + FrequencyList_reset(instance->hopper_frequencies); + } else { + FURI_LOG_I(TAG, "Keeping standard frequencies"); + } + + // Load frequencies if(!flipper_format_rewind(fff_data_file)) { FURI_LOG_E(TAG, "Rewind error"); break; } - i = 0; while(flipper_format_read_uint32( - fff_data_file, "Frequency", (uint32_t*)&temp_data32, 1)) { + fff_data_file, "frequency", (uint32_t*)&temp_data32, 1)) { if(furi_hal_subghz_is_frequency_valid(temp_data32)) { FURI_LOG_I(TAG, "Frequency loaded %lu", temp_data32); - FrequenciesList_push_back(instance->frequencies, temp_data32); - i++; + FrequencyList_push_back(instance->frequencies, temp_data32); } else { FURI_LOG_E(TAG, "Frequency not supported %lu", temp_data32); } } - instance->frequencies_count = i; + // Load hopper frequencies if(!flipper_format_rewind(fff_data_file)) { FURI_LOG_E(TAG, "Rewind error"); break; } - i = 0; while(flipper_format_read_uint32( - fff_data_file, "Hopper_frequency", (uint32_t*)&temp_data32, 1)) { + fff_data_file, "hopper_frequency", (uint32_t*)&temp_data32, 1)) { if(furi_hal_subghz_is_frequency_valid(temp_data32)) { FURI_LOG_I(TAG, "Hopper frequency loaded %lu", temp_data32); - FrequenciesList_push_back(instance->hopper_frequencies, temp_data32); - i++; + FrequencyList_push_back(instance->hopper_frequencies, temp_data32); } else { FURI_LOG_E(TAG, "Hopper frequency not supported %lu", temp_data32); } } - instance->hopper_frequencies_count = i; + // Default frequency (optional) if(!flipper_format_rewind(fff_data_file)) { FURI_LOG_E(TAG, "Rewind error"); break; } - if(!flipper_format_read_uint32( - fff_data_file, "Frequency_default", (uint32_t*)&temp_data32, 1)) { - FURI_LOG_E(TAG, "Frequency default missing"); - break; - } - - for(i = 0; i < instance->frequencies_count; i++) { - if(subghz_setting_get_frequency(instance, i) == temp_data32) { - instance->frequency_default_index = i; - FURI_LOG_I(TAG, "Frequency default index %lu", i); - loading = SubGhzSettingStateLoadFrequencyDefault; - break; - } - } - - if(loading == SubGhzSettingStateLoadFrequencyDefault) { - loading = SubGhzSettingStateOkLoad; - } else { - FURI_LOG_E(TAG, "Frequency default index missing"); + if(flipper_format_read_uint32(fff_data_file, "default_frequency", &temp_data32, 1)) { + for + M_EACH(frequency, instance->frequencies, FrequencyList_t) { + *frequency &= FREQUENCY_MASK; + if(*frequency == temp_data32) { + *frequency |= FREQUENCY_FLAG_DEFAULT; + } + } } } while(false); } @@ -301,67 +320,56 @@ void subghz_setting_load(SubGhzSetting* instance, const char* file_path) { flipper_format_free(fff_data_file); furi_record_close("storage"); - if(loading != SubGhzSettingStateOkLoad) { - switch(furi_hal_version_get_hw_region()) { - case FuriHalVersionRegionEuRu: - subghz_setting_load_default( - instance, - subghz_frequencies_region_eu_ru, - subghz_hopper_frequencies_region_eu_ru, - subghz_frequency_default_index_region_eu_ru); - break; - case FuriHalVersionRegionUsCaAu: - subghz_setting_load_default( - instance, - subghz_frequencies_region_us_ca_au, - subghz_hopper_frequencies_region_us_ca_au, - subghz_frequency_default_index_region_us_ca_au); - break; - case FuriHalVersionRegionJp: - subghz_setting_load_default( - instance, - subghz_frequencies_region_jp, - subghz_hopper_frequencies_region_jp, - subghz_frequency_default_index_region_jp); - break; - - default: - subghz_setting_load_default( - instance, - subghz_frequencies, - subghz_hopper_frequencies, - subghz_frequency_default_index); - break; - } + if(!FrequencyList_size(instance->frequencies) || + !FrequencyList_size(instance->hopper_frequencies)) { + FURI_LOG_E(TAG, "Error loading user settings, loading default settings"); + subghz_setting_load_default(instance); } } size_t subghz_setting_get_frequency_count(SubGhzSetting* instance) { furi_assert(instance); - return instance->frequencies_count; + return FrequencyList_size(instance->frequencies); } size_t subghz_setting_get_hopper_frequency_count(SubGhzSetting* instance) { furi_assert(instance); - return instance->hopper_frequencies_count; + return FrequencyList_size(instance->hopper_frequencies); } uint32_t subghz_setting_get_frequency(SubGhzSetting* instance, size_t idx) { furi_assert(instance); - return *FrequenciesList_get(instance->frequencies, idx); + uint32_t* ret = FrequencyList_get(instance->frequencies, idx); + if(ret) { + return (*ret) & FREQUENCY_MASK; + } else { + return 0; + } } uint32_t subghz_setting_get_hopper_frequency(SubGhzSetting* instance, size_t idx) { furi_assert(instance); - return *FrequenciesList_get(instance->hopper_frequencies, idx); + uint32_t* ret = FrequencyList_get(instance->hopper_frequencies, idx); + if(ret) { + return *ret; + } else { + return 0; + } } uint32_t subghz_setting_get_frequency_default_index(SubGhzSetting* instance) { furi_assert(instance); - return instance->frequency_default_index; + for(size_t i = 0; i < FrequencyList_size(instance->frequencies); i++) { + uint32_t frequency = *FrequencyList_get(instance->frequencies, i); + if(frequency & FREQUENCY_FLAG_DEFAULT) { + return i; + } + } + return 0; } uint32_t subghz_setting_get_default_frequency(SubGhzSetting* instance) { furi_assert(instance); - return *FrequenciesList_get(instance->frequencies, instance->frequency_default_index); + return subghz_setting_get_frequency( + instance, subghz_setting_get_frequency_default_index(instance)); } diff --git a/applications/subghz/views/receiver.c b/applications/subghz/views/receiver.c index 7b19cbcf..866d8272 100644 --- a/applications/subghz/views/receiver.c +++ b/applications/subghz/views/receiver.c @@ -111,9 +111,9 @@ void subghz_view_receiver_add_data_statusbar( furi_assert(subghz_receiver); with_view_model( subghz_receiver->view, (SubGhzViewReceiverModel * model) { - string_set(model->frequency_str, frequency_str); - string_set(model->preset_str, preset_str); - string_set(model->history_stat_str, history_stat_str); + string_set_str(model->frequency_str, frequency_str); + string_set_str(model->preset_str, preset_str); + string_set_str(model->history_stat_str, history_stat_str); return true; }); } diff --git a/applications/subghz/views/subghz_frequency_analyzer.c b/applications/subghz/views/subghz_frequency_analyzer.c index ec3c5ee9..d3f77315 100644 --- a/applications/subghz/views/subghz_frequency_analyzer.c +++ b/applications/subghz/views/subghz_frequency_analyzer.c @@ -111,7 +111,7 @@ void subghz_frequency_analyzer_enter(void* context) { SubGhzFrequencyAnalyzer* instance = context; //Start worker - instance->worker = subghz_frequency_analyzer_worker_alloc(); + instance->worker = subghz_frequency_analyzer_worker_alloc(instance->context); subghz_frequency_analyzer_worker_set_pair_callback( instance->worker, diff --git a/applications/subghz/views/subghz_read_raw.c b/applications/subghz/views/subghz_read_raw.c index ff3ba45a..a4807d77 100644 --- a/applications/subghz/views/subghz_read_raw.c +++ b/applications/subghz/views/subghz_read_raw.c @@ -46,8 +46,8 @@ void subghz_read_raw_add_data_statusbar( furi_assert(instance); with_view_model( instance->view, (SubGhzReadRAWModel * model) { - string_set(model->frequency_str, frequency_str); - string_set(model->preset_str, preset_str); + string_set_str(model->frequency_str, frequency_str); + string_set_str(model->preset_str, preset_str); return true; }); } @@ -372,7 +372,7 @@ bool subghz_read_raw_input(InputEvent* event, void* context) { model->satus = SubGhzReadRAWStatusStart; model->rssi_history_end = false; model->ind_write = 0; - string_set(model->sample_write, "0 spl."); + string_set_str(model->sample_write, "0 spl."); string_reset(model->file_name); instance->callback(SubGhzCustomEventViewReadRAWErase, instance->context); } @@ -424,7 +424,7 @@ void subghz_read_raw_set_status( model->rssi_history_end = false; model->ind_write = 0; string_reset(model->file_name); - string_set(model->sample_write, "0 spl."); + string_set_str(model->sample_write, "0 spl."); return true; }); break; @@ -441,8 +441,8 @@ void subghz_read_raw_set_status( model->satus = SubGhzReadRAWStatusLoadKeyIDLE; model->rssi_history_end = false; model->ind_write = 0; - string_set(model->file_name, file_name); - string_set(model->sample_write, "RAW"); + string_set_str(model->file_name, file_name); + string_set_str(model->sample_write, "RAW"); return true; }); break; @@ -451,8 +451,8 @@ void subghz_read_raw_set_status( instance->view, (SubGhzReadRAWModel * model) { model->satus = SubGhzReadRAWStatusLoadKeyIDLE; if(!model->ind_write) { - string_set(model->file_name, file_name); - string_set(model->sample_write, "RAW"); + string_set_str(model->file_name, file_name); + string_set_str(model->sample_write, "RAW"); } else { string_reset(model->file_name); } diff --git a/applications/subghz/views/transmitter.c b/applications/subghz/views/transmitter.c index 3113e31f..3cbcf098 100644 --- a/applications/subghz/views/transmitter.c +++ b/applications/subghz/views/transmitter.c @@ -36,9 +36,9 @@ void subghz_view_transmitter_add_data_to_show( furi_assert(subghz_transmitter); with_view_model( subghz_transmitter->view, (SubGhzViewTransmitterModel * model) { - string_set(model->key_str, key_str); - string_set(model->frequency_str, frequency_str); - string_set(model->preset_str, preset_str); + string_set_str(model->key_str, key_str); + string_set_str(model->frequency_str, frequency_str); + string_set_str(model->preset_str, preset_str); model->show_button = show_button; return true; }); diff --git a/applications/system/system_settings.c b/applications/system/system_settings.c index 97017c8d..7bbcdd7b 100644 --- a/applications/system/system_settings.c +++ b/applications/system/system_settings.c @@ -30,8 +30,8 @@ static void log_level_changed(VariableItem* item) { } const char* const debug_text[] = { - "Disable", - "Enable", + "OFF", + "ON", }; static void debug_changed(VariableItem* item) { diff --git a/applications/unit_tests/subghz/subghz_test.c b/applications/unit_tests/subghz/subghz_test.c index 879deae8..c8f23780 100644 --- a/applications/unit_tests/subghz/subghz_test.c +++ b/applications/unit_tests/subghz/subghz_test.c @@ -328,10 +328,10 @@ MU_TEST(subghz_decoder_star_line_test) { "Test decoder " SUBGHZ_PROTOCOL_STAR_LINE_NAME " error\r\n"); } -MU_TEST(subghz_decoder_firefly_test) { +MU_TEST(subghz_decoder_linear_test) { mu_assert( - subghz_decoder_test("/ext/unit_tests/subghz/firefly_raw.sub", SUBGHZ_PROTOCOL_FIREFLY_NAME), - "Test decoder " SUBGHZ_PROTOCOL_FIREFLY_NAME " error\r\n"); + subghz_decoder_test("/ext/unit_tests/subghz/linear_raw.sub", SUBGHZ_PROTOCOL_LINEAR_NAME), + "Test decoder " SUBGHZ_PROTOCOL_LINEAR_NAME " error\r\n"); } MU_TEST(subghz_decoder_megacode_test) { @@ -398,10 +398,10 @@ MU_TEST(subghz_encoder_keelog_test) { "Test encoder " SUBGHZ_PROTOCOL_KEELOQ_NAME " error\r\n"); } -MU_TEST(subghz_encoder_firefly_test) { +MU_TEST(subghz_encoder_linear_test) { mu_assert( - subghz_encoder_test("/ext/unit_tests/subghz/firely.sub"), - "Test encoder " SUBGHZ_PROTOCOL_FIREFLY_NAME " error\r\n"); + subghz_encoder_test("/ext/unit_tests/subghz/linear.sub"), + "Test encoder " SUBGHZ_PROTOCOL_LINEAR_NAME " error\r\n"); } MU_TEST(subghz_encoder_megacode_test) { @@ -416,6 +416,18 @@ MU_TEST(subghz_encoder_holtek_test) { "Test encoder " SUBGHZ_PROTOCOL_HOLTEK_NAME " error\r\n"); } +MU_TEST(subghz_encoder_secplus_v1_test) { + mu_assert( + subghz_encoder_test("/ext/unit_tests/subghz/security_pls_1_0.sub"), + "Test encoder " SUBGHZ_PROTOCOL_SECPLUS_V1_NAME " error\r\n"); +} + +MU_TEST(subghz_encoder_secplus_v2_test) { + mu_assert( + subghz_encoder_test("/ext/unit_tests/subghz/security_pls_2_0.sub"), + "Test encoder " SUBGHZ_PROTOCOL_SECPLUS_V2_NAME " error\r\n"); +} + MU_TEST(subghz_random_test) { mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n"); } @@ -442,7 +454,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_decoder_somfy_keytis_test); MU_RUN_TEST(subghz_decoder_somfy_telis_test); MU_RUN_TEST(subghz_decoder_star_line_test); - MU_RUN_TEST(subghz_decoder_firefly_test); + MU_RUN_TEST(subghz_decoder_linear_test); MU_RUN_TEST(subghz_decoder_megacode_test); MU_RUN_TEST(subghz_decoder_secplus_v1_test); MU_RUN_TEST(subghz_decoder_secplus_v2_test); @@ -454,9 +466,11 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_encoder_gate_tx_test); MU_RUN_TEST(subghz_encoder_nice_flo_test); MU_RUN_TEST(subghz_encoder_keelog_test); - MU_RUN_TEST(subghz_encoder_firefly_test); + MU_RUN_TEST(subghz_encoder_linear_test); MU_RUN_TEST(subghz_encoder_megacode_test); MU_RUN_TEST(subghz_encoder_holtek_test); + MU_RUN_TEST(subghz_encoder_secplus_v1_test); + MU_RUN_TEST(subghz_encoder_secplus_v2_test); MU_RUN_TEST(subghz_random_test); subghz_test_deinit(); diff --git a/assets/compiled/assets_icons.c b/assets/compiled/assets_icons.c index 709fab45..fb125095 100644 --- a/assets/compiled/assets_icons.c +++ b/assets/compiled/assets_icons.c @@ -40,6 +40,9 @@ const uint8_t* const _I_125_10px[] = {_I_125_10px_0}; const uint8_t _I_Nfc_10px_0[] = {0x00,0x80,0x00,0x00,0x01,0x22,0x02,0x43,0x02,0x45,0x02,0x49,0x02,0x31,0x02,0x22,0x02,0x00,0x01,0x80,0x00,}; const uint8_t* const _I_Nfc_10px[] = {_I_Nfc_10px_0}; +const uint8_t _I_back_10px_0[] = {0x00,0x00,0x00,0x10,0x00,0x38,0x00,0x7C,0x00,0xFE,0x00,0x38,0x00,0x38,0x00,0xF8,0x01,0xF8,0x01,0x00,0x00,}; +const uint8_t* const _I_back_10px[] = {_I_back_10px_0}; + const uint8_t _I_badusb_10px_0[] = {0x01,0x00,0x11,0x00,0x00,0x0f,0xe2,0x01,0xfc,0x80,0xdd,0x20,0x32,0x48,0x08,0x14,0x40,0x23,0xa8,0x08,0xa0,}; const uint8_t* const _I_badusb_10px[] = {_I_badusb_10px_0}; @@ -55,6 +58,12 @@ const uint8_t* const _I_ibutt_10px[] = {_I_ibutt_10px_0}; const uint8_t _I_ir_10px_0[] = {0x00,0xFC,0x00,0x02,0x01,0x79,0x02,0x84,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x58,0x00,0x78,0x00,0xFF,0x03,}; const uint8_t* const _I_ir_10px[] = {_I_ir_10px_0}; +const uint8_t _I_loading_10px_0[] = {0x00,0xFE,0x00,0x82,0x00,0xBA,0x00,0x54,0x00,0x28,0x00,0x28,0x00,0x44,0x00,0x92,0x00,0xBA,0x00,0xFE,0x00,}; +const uint8_t* const _I_loading_10px[] = {_I_loading_10px_0}; + +const uint8_t _I_music_10px_0[] = {0x01,0x00,0x10,0x00,0xf0,0x00,0x46,0x03,0x20,0x80,0x00,0x4e,0x7d,0x00,0x9f,0x80,0x4a,0x3c,0x13,0x20,}; +const uint8_t* const _I_music_10px[] = {_I_music_10px_0}; + const uint8_t _I_sub1_10px_0[] = {0x01,0x00,0x12,0x00,0x81,0x40,0x69,0x30,0x2c,0x2c,0x0b,0x6a,0x01,0x28,0x0c,0x0a,0x65,0x01,0x98,0x40,0x00,0x26,}; const uint8_t* const _I_sub1_10px[] = {_I_sub1_10px_0}; @@ -190,9 +199,6 @@ const uint8_t* const _I_DolphinFirstStart8_56x51[] = {_I_DolphinFirstStart8_56x5 const uint8_t _I_DolphinOkay_41x43_0[] = {0x01,0x00,0xa0,0x00,0x00,0x0f,0x82,0x3e,0x05,0x38,0xf7,0x80,0x08,0x58,0x08,0x0c,0x02,0x0e,0x05,0x1b,0x00,0x08,0x63,0x00,0x21,0x88,0x00,0x86,0x40,0x02,0x18,0x40,0x08,0x68,0x00,0x21,0x82,0x06,0x88,0x0a,0xf0,0x21,0x39,0x09,0x84,0x02,0x20,0x57,0x09,0x98,0x15,0x67,0xc0,0x54,0xbe,0x81,0x4f,0x01,0xfe,0x02,0x9d,0x03,0xc4,0x20,0x10,0x29,0x7c,0x80,0xa9,0xfe,0x02,0xac,0x14,0x0a,0x77,0xc8,0x58,0x8c,0xf0,0x11,0x51,0x79,0xff,0x61,0x44,0x93,0x81,0x02,0xc4,0x9e,0x60,0xb2,0xf0,0xa0,0x46,0x0c,0x17,0x14,0x99,0x1a,0x07,0x80,0x59,0x49,0x82,0x21,0xc0,0xa4,0x82,0x24,0xb9,0x20,0x88,0x1c,0x47,0xc2,0x07,0x11,0x54,0xa0,0x60,0x53,0xb8,0x0a,0x4b,0xf3,0x03,0x87,0x81,0x4a,0x0d,0xfc,0x1a,0x98,0x68,0xb8,0x01,0x51,0x13,0x15,0xe0,0x82,0x7f,0x8d,0x78,0x38,0xbf,0xff,0xfa,0xb8,0x60,0xbf,0x1b,0xf9,0x50,0x14,0xea,0xe7,0x02,0x02,0x8e,0xac,0x94,0x40,}; const uint8_t* const _I_DolphinOkay_41x43[] = {_I_DolphinOkay_41x43_0}; -const uint8_t _I_Flipper_young_80x60_0[] = {0x01,0x00,0xa3,0x01,0x00,0x1e,0x03,0xff,0xff,0x87,0x82,0x57,0xf1,0x83,0x90,0xde,0x01,0x2b,0x0e,0x83,0x70,0xfb,0x10,0x10,0x41,0xf8,0x27,0x70,0xcc,0x34,0xc6,0x0e,0x09,0x3e,0x04,0x86,0x21,0x0c,0x90,0xc3,0x03,0xa9,0xe7,0xb0,0x46,0x2c,0x51,0x40,0x4a,0x63,0x38,0x31,0x0a,0x34,0x90,0x12,0x91,0x8e,0x3c,0xff,0x89,0x4c,0x04,0xa4,0x43,0xfd,0xf3,0xc3,0xf2,0x01,0x29,0xe0,0x2b,0x8e,0x72,0xa0,0x46,0x4b,0xe0,0x30,0xba,0x10,0x22,0xca,0x1c,0x0b,0x26,0x09,0x3c,0x04,0x0c,0x08,0x59,0xc8,0x21,0x64,0xc4,0x47,0x98,0x82,0x81,0x0a,0xe0,0x21,0x39,0x04,0x34,0x88,0x60,0x93,0xa0,0x45,0x4b,0x06,0xa3,0x40,0x48,0xfc,0x20,0xf0,0x82,0xa2,0x4d,0x60,0x11,0xe9,0xc2,0x19,0x64,0xd0,0x08,0x1f,0x80,0x7e,0x60,0x01,0x92,0x60,0x20,0x38,0x05,0x21,0x7c,0x3f,0xf0,0x1a,0xe6,0x00,0xe6,0x21,0x32,0x1a,0x0c,0x0e,0x91,0x80,0x8f,0xc0,0x06,0x25,0xcc,0xbf,0xc1,0xaa,0x10,0x0b,0xfc,0x02,0x60,0x2e,0x2c,0x04,0x32,0xc1,0x00,0xff,0x40,0x68,0x00,0x91,0x89,0xc0,0x21,0x20,0x51,0xfe,0x41,0xf0,0x00,0x91,0xc4,0xcf,0xe2,0x40,0x51,0xfc,0x0c,0x86,0x07,0x80,0xe2,0xdf,0xda,0x25,0xf0,0x9f,0xc0,0x21,0x98,0x0f,0x27,0xfd,0xa2,0x5e,0x01,0x90,0xc4,0x30,0x1e,0x2f,0xfc,0xa1,0x3a,0x45,0x41,0xb0,0x60,0x3e,0x5e,0x79,0x4a,0x10,0xbf,0xe2,0x61,0xc0,0x82,0x52,0x01,0xff,0x36,0x8e,0x3b,0xe5,0xff,0x04,0x9f,0xf8,0x78,0x3b,0x8f,0x97,0xf8,0x12,0x7f,0xc3,0x78,0xf8,0x3e,0x5f,0xc0,0x49,0xfe,0x08,0xc2,0x17,0x1f,0xcd,0xa5,0xac,0x5f,0x02,0x30,0xc0,0x30,0x5f,0xfd,0x23,0xbc,0xbc,0x1f,0xf0,0xc1,0x5f,0xaa,0x8e,0x52,0x28,0x10,0x10,0x6f,0x1b,0x28,0x57,0x81,0x66,0x25,0x01,0x80,0x4e,0x28,0x15,0x98,0xad,0xc3,0xfd,0xff,0xff,0x91,0x87,0xc1,0x80,0xd4,0xc2,0xb2,0x03,0xb1,0x5b,0x13,0x34,0x6a,0xf1,0x58,0x84,0x0e,0x1d,0x00,0x23,0x14,0x0f,0x55,0x0a,0x88,0x67,0x0d,0x83,0x7c,0x04,0x8c,0x0a,0xa9,0x15,0x90,0x7c,0x07,0x23,0xf8,0x80,0xc1,0xa0,0xda,0x88,0x54,0x82,0x00,0x2f,0x1f,0xe4,0x3c,0x7a,0x35,0x08,0xab,0x20,0x7f,0x03,0xc1,0x2d,0x96,0x82,0x14,0xce,0x20,0x02,0x04,0xc6,0x00,0x60,0x20,0x01,0x84,0xc4,0x6a,0x21,0x36,0x3b,0x8c,0xf0,0x3c,0xc8,0x02,0x1b,0x88,0x01,0xe1,0x80,0x98,0x2d,0x10,0x01,0xb0,0x05,0xa1,0x00,0x3d,0xf8,0x13,0x17,0x81,0x47,0x80,0x0b,0xc0,0x28,0x8e,0x02,0xa4,0x81,0x2c,0xf0,0x20,0x01,0x00,}; -const uint8_t* const _I_Flipper_young_80x60[] = {_I_Flipper_young_80x60_0}; - const uint8_t _I_ArrowDownEmpty_14x15_0[] = {0x01,0x00,0x17,0x00,0xfc,0x41,0xe1,0x10,0x40,0x0c,0xc3,0xe7,0x90,0x19,0x04,0x0a,0x20,0x08,0x10,0x48,0xc4,0x20,0x52,0x08,0x0f,0x02,0x00,}; const uint8_t* const _I_ArrowDownEmpty_14x15[] = {_I_ArrowDownEmpty_14x15_0}; @@ -205,9 +211,6 @@ const uint8_t* const _I_ArrowUpEmpty_14x15[] = {_I_ArrowUpEmpty_14x15_0}; const uint8_t _I_ArrowUpFilled_14x15_0[] = {0x00,0xC0,0x00,0x20,0x01,0xD0,0x02,0xE8,0x05,0xF4,0x0B,0xFA,0x17,0x61,0x21,0xAF,0x3D,0x68,0x05,0xA8,0x05,0x68,0x05,0xA8,0x05,0xE8,0x05,0x08,0x04,0xF8,0x07,}; const uint8_t* const _I_ArrowUpFilled_14x15[] = {_I_ArrowUpFilled_14x15_0}; -const uint8_t _I_Back_15x10_0[] = {0x00,0x04,0x00,0x06,0x00,0xFF,0x0F,0x06,0x10,0x04,0x20,0x00,0x40,0x00,0x40,0x00,0x20,0x00,0x10,0xFE,0x0F,}; -const uint8_t* const _I_Back_15x10[] = {_I_Back_15x10_0}; - const uint8_t _I_DolphinReadingSuccess_59x63_0[] = {0x01,0x00,0x19,0x01,0x00,0x1d,0x00,0x0f,0xd2,0x00,0x21,0xe0,0x3f,0xf0,0xf9,0x00,0x40,0xee,0x00,0x11,0x88,0x04,0x0e,0x18,0x11,0x18,0x8c,0x40,0x0e,0x50,0x30,0x10,0xc0,0xa1,0x01,0xe2,0x05,0x14,0x12,0x08,0x33,0x58,0x44,0x08,0x66,0xa1,0xe3,0x01,0x9c,0x83,0x00,0x24,0x11,0x11,0x06,0xc4,0x76,0x20,0x75,0x15,0x99,0x48,0xc0,0xe9,0x0f,0x03,0x95,0xfc,0x86,0x3c,0x09,0x80,0x1c,0x7c,0x00,0x91,0x81,0x48,0x2f,0xc1,0x41,0x8c,0xc0,0x20,0x30,0x1c,0x87,0xfc,0x0e,0x30,0x70,0x70,0x81,0xc7,0xe6,0x07,0x18,0x08,0x1c,0xb9,0x1e,0x38,0x0f,0x02,0x01,0xf0,0x03,0xa0,0xa4,0x7f,0x90,0x30,0x38,0xff,0xe0,0x28,0x21,0xff,0x06,0x44,0x0e,0x46,0xe1,0x01,0x8c,0x03,0x34,0x2f,0x25,0x18,0x80,0xc7,0x2a,0x03,0x2e,0x01,0x3c,0x70,0x12,0xa2,0x39,0x78,0x27,0xe0,0x31,0xea,0x82,0xc4,0x6c,0x31,0xf0,0x78,0xea,0xb0,0x22,0x31,0xfc,0x1a,0xc6,0x01,0x55,0x25,0x88,0xf8,0x4b,0x02,0x1f,0x13,0xe1,0x7f,0x97,0x85,0x15,0x03,0x90,0xf8,0xa0,0x10,0xa1,0xb1,0x0e,0x88,0x00,0x7f,0x0f,0xc0,0x7c,0x57,0x27,0x3c,0xb0,0x7f,0x5f,0xa9,0x1f,0xc0,0x6a,0xc5,0x05,0xc0,0xf0,0x11,0x46,0xac,0x18,0x3f,0xf9,0x54,0x75,0x00,0x73,0x1f,0x0f,0xfe,0xfe,0xc6,0x30,0x01,0xbc,0x48,0x00,0x84,0x82,0x00,0x1b,0x64,0xc0,0x07,0x60,0x03,0xb4,0x70,0x0c,0xbf,0x82,0x31,0x01,0x8d,0x0c,0x40,0x02,0x37,0x08,0x1d,0x74,0x00,0x76,0xa0,0x01,0xdb,0x01,0xfe,0x85,0x8b,0x96,0xaa,0x9b,0x30,0x01,0x6a,0xa3,0x40,0x75,0xaa,0x03,0xdb,0x50,0xbb,0x30,0x01,0x54,0x24,0x25,0xe6,0x51,0x08,0x1f,0x68,0x00,0x7f,0x03,0xf2,0x79,0xc0,0xf4,}; const uint8_t* const _I_DolphinReadingSuccess_59x63[] = {_I_DolphinReadingSuccess_59x63_0}; @@ -217,9 +220,6 @@ const uint8_t* const _I_Down_25x27[] = {_I_Down_25x27_0}; const uint8_t _I_Down_hvr_25x27_0[] = {0x01,0x00,0x3a,0x00,0xfc,0x7f,0xe7,0xf0,0x0f,0xe7,0xfe,0xff,0x00,0xff,0x7f,0xff,0xf0,0x00,0x10,0xff,0xe0,0x20,0x3f,0x01,0x9c,0x3e,0x01,0xe0,0x01,0xa4,0x7e,0x01,0xf0,0x80,0x8b,0x47,0xf1,0x01,0x16,0x8f,0xf0,0x2e,0x23,0x11,0x01,0x88,0x04,0xf0,0x60,0x32,0xe3,0x80,0xcb,0xde,0x37,0xf0,0x1a,0x95,0xcc,0xbe,0x66,0x73,}; const uint8_t* const _I_Down_hvr_25x27[] = {_I_Down_hvr_25x27_0}; -const uint8_t _I_Fill_marker_7x7_0[] = {0x00,0x1C,0x32,0x6F,0x5F,0x7F,0x3E,0x1C,}; -const uint8_t* const _I_Fill_marker_7x7[] = {_I_Fill_marker_7x7_0}; - const uint8_t _I_InfraredArrowDown_4x8_0[] = {0x00,0xFF,0x7E,0x3C,0x18,}; const uint8_t* const _I_InfraredArrowDown_4x8[] = {_I_InfraredArrowDown_4x8_0}; @@ -268,9 +268,6 @@ const uint8_t* const _I_Vol_up_25x27[] = {_I_Vol_up_25x27_0}; const uint8_t _I_Vol_up_hvr_25x27_0[] = {0x01,0x00,0x28,0x00,0xfc,0x7f,0xe7,0xf0,0x0f,0xe7,0xfe,0xff,0x00,0xff,0x7f,0xff,0xf0,0x00,0x10,0xff,0xe0,0x20,0x38,0xf7,0x80,0xfc,0x06,0xa2,0xd1,0xfc,0x00,0xd0,0x2f,0xe0,0x38,0x21,0xd8,0x0c,0x8a,0xe6,0x5f,0x33,0x39,0x80,}; const uint8_t* const _I_Vol_up_hvr_25x27[] = {_I_Vol_up_hvr_25x27_0}; -const uint8_t _I_Back3_45x8_0[] = {0x00,0x04,0x00,0x10,0x00,0x40,0x00,0x06,0x00,0x18,0x00,0x60,0x00,0x7F,0x00,0xFC,0x01,0xF0,0x07,0x86,0x20,0x18,0x82,0x60,0x08,0x04,0x71,0x10,0xC4,0x41,0x10,0x00,0x21,0x00,0x84,0x00,0x10,0x80,0x00,0x00,0x02,0x00,0x08,0x7E,0x00,0xF8,0x01,0xE0,0x07,}; -const uint8_t* const _I_Back3_45x8[] = {_I_Back3_45x8_0}; - const uint8_t _I_DoorLeft_70x55_0[] = {0x01,0x00,0x19,0x01,0x00,0x2c,0x32,0x01,0x03,0x04,0x2c,0x18,0x10,0xf0,0x40,0x47,0x82,0x06,0x81,0x03,0xff,0x80,0x08,0x1a,0x20,0x82,0x15,0x28,0x21,0x87,0x82,0x08,0x6f,0xc0,0xb1,0xe6,0x10,0x10,0x8b,0x46,0x20,0x43,0x55,0x8f,0x82,0x10,0x32,0x73,0x0a,0x09,0x89,0x6c,0x1e,0x09,0x00,0x18,0x60,0xf0,0x0c,0x84,0x93,0x82,0x03,0x18,0x0c,0x02,0x1d,0x00,0x90,0x52,0x70,0x50,0x1e,0x00,0x58,0x63,0x90,0x0a,0x06,0x4a,0x09,0x03,0xb0,0x02,0x06,0x70,0x62,0x49,0xf8,0x0c,0x66,0x3f,0xf0,0x41,0x63,0x04,0x43,0x00,0x99,0x60,0x00,0x85,0xc8,0x06,0x14,0xd0,0x80,0x3f,0xc8,0x0d,0xb8,0x10,0x70,0xf8,0x34,0x13,0x03,0x39,0x04,0x1c,0x42,0x19,0xf8,0xa0,0xc2,0x01,0x07,0xef,0x02,0x8c,0x80,0x10,0x9d,0x00,0x43,0xec,0x00,0xa3,0x10,0x04,0x25,0xce,0x19,0xfc,0x88,0x82,0x12,0x0c,0x35,0x10,0x42,0x4c,0xa1,0x90,0x3f,0xc0,0x21,0x22,0x39,0x82,0xc8,0x88,0xd2,0x11,0xf0,0x01,0x88,0xd5,0x18,0xe2,0x08,0x68,0x10,0x0c,0xa8,0x00,0x83,0x81,0xcc,0xd5,0xc3,0x80,0x84,0x82,0x0e,0xcc,0xc0,0x15,0x79,0x02,0x0b,0x98,0xf8,0x11,0x88,0x82,0x0f,0x31,0x19,0x02,0x08,0x2c,0x9f,0x6a,0x1d,0x20,0x41,0x31,0x4c,0x10,0x8d,0x73,0x04,0x23,0xa4,0xc4,0x6c,0xde,0x20,0x42,0xcc,0x01,0x07,0x07,0xff,0x80,0x06,0x3e,0x08,0x38,0x70,0x20,0xa1,0xe0,0x83,0x8e,0x01,0x0c,0xf0,0x73,0x80,0x43,0x70,0x05,0x08,0x00,0x2c,0x04,0xc4,0x46,0x53,0x09,0x98,0x24,0x80,0x65,0x80,0xb0,0xd9,0x84,0x65,0x32,0x06,0x17,0x0f,0x98,0x23,0x63,0xe1,0x88,0xc4,0x08,0x5f,0xc1,0x30,0x9d,0x84,0x4e,0x66,0x94,0x11,0x98,0x75,0x26,0x00,}; const uint8_t* const _I_DoorLeft_70x55[] = {_I_DoorLeft_70x55_0}; @@ -558,9 +555,6 @@ const uint8_t* const _I_RFIDDolphinSend_97x61[] = {_I_RFIDDolphinSend_97x61_0}; const uint8_t _I_RFIDDolphinSuccess_108x57_0[] = {0x01,0x00,0xe7,0x01,0x00,0x0f,0x03,0xff,0x1f,0x06,0xd4,0xe2,0x01,0xe0,0x06,0xd4,0x18,0x04,0x30,0x30,0x64,0x60,0x20,0x20,0x31,0x86,0x03,0x62,0x80,0x03,0x28,0x80,0x36,0x24,0x00,0x36,0x00,0x28,0x5c,0xc3,0xe6,0x00,0x58,0x40,0xec,0xc1,0xb1,0x04,0x02,0x19,0x24,0x80,0x0b,0x02,0x02,0x40,0x37,0xc4,0x8c,0x2e,0x40,0x6f,0x93,0x8b,0x81,0x07,0x06,0xdc,0xc2,0x38,0x66,0x50,0x6a,0xe2,0x27,0xe0,0xd2,0xfc,0x08,0x09,0x0c,0x9c,0x4b,0x98,0x34,0xa0,0xe1,0xd5,0x06,0x8f,0x92,0xc2,0x05,0x1e,0x42,0xe1,0x81,0xa3,0xe2,0xf0,0xbc,0x4c,0x1a,0xff,0x2f,0x9b,0x80,0xd8,0xca,0x05,0x1f,0x97,0xfd,0xf8,0x60,0xd2,0x01,0x1e,0x00,0x1a,0x5c,0x00,0x08,0xc9,0xc1,0xab,0x40,0xf9,0x83,0x46,0x61,0x00,0xd8,0x4a,0x81,0xab,0xa0,0xf3,0x5f,0xc6,0x05,0x58,0x8a,0xa4,0x09,0x76,0x21,0xb1,0xf2,0x83,0x4f,0x5d,0x1a,0x01,0x8c,0x90,0x1a,0x31,0x0d,0x07,0xa9,0x16,0x50,0x0a,0xac,0x34,0xba,0x42,0xa1,0x88,0x50,0x23,0xaa,0x72,0xe0,0x6a,0xa1,0x4a,0x32,0x39,0x88,0x6c,0x60,0xc7,0x82,0xb0,0x55,0x60,0xa2,0x92,0x80,0xc0,0x43,0x63,0x03,0x25,0x96,0xe3,0x54,0x33,0x18,0xc4,0x90,0x22,0x21,0x81,0x81,0x03,0x4a,0xa9,0x55,0x7a,0x17,0xf3,0x82,0x9f,0x6d,0x5e,0xa9,0xb6,0x50,0x38,0x70,0x35,0x70,0x15,0x5a,0xa9,0xb8,0xa3,0x46,0x12,0x06,0x9f,0x83,0x54,0x8a,0x28,0x80,0x34,0xfc,0x08,0x93,0xaa,0xc7,0x40,0x83,0x83,0x81,0xd3,0xa1,0xd1,0x08,0x84,0x0c,0x24,0x3f,0xed,0x54,0x18,0x26,0x50,0x20,0xd9,0x42,0x21,0x90,0x4c,0x07,0xff,0xae,0x52,0x20,0x6a,0xc4,0x23,0x1f,0x88,0x3f,0xf0,0x1a,0x45,0x31,0xe7,0x03,0x4a,0x41,0xe0,0x69,0x0f,0xc2,0x1e,0x0d,0x19,0x80,0x48,0xa2,0x10,0xc5,0x68,0xdf,0x0a,0x82,0xb9,0x28,0x22,0x2c,0xe3,0x0a,0xd1,0x2b,0x0f,0x00,0x3c,0x22,0x91,0x53,0x9c,0x50,0x1a,0x30,0x08,0x39,0x1c,0x60,0x6d,0x12,0x3d,0x8c,0xc2,0x51,0x00,0x17,0x0c,0xe2,0x01,0xff,0x83,0x84,0xc6,0x40,0xb0,0x19,0x84,0xd0,0x1a,0x5c,0x08,0x1f,0xf8,0x8c,0x50,0x43,0x08,0xce,0x2d,0x06,0x71,0x5f,0x17,0xfe,0x12,0xdf,0x20,0x69,0x55,0x01,0xa6,0x00,0x18,0x40,0xa4,0x80,0x63,0x3c,0xb5,0x03,0x56,0x08,0x8b,0x20,0x10,0xcf,0x03,0x62,0x08,0x20,0x00,0x94,0xc6,0x01,0x70,0x01,0x0c,0xe8,0x36,0x20,0xd3,0xe0,0x00,0xcb,0x10,0x02,0x19,0xf3,0x9c,0x41,0xa3,0x15,0x31,0x90,0x00,0x70,0xc0,0x21,0xdd,0x86,0xc4,0x78,0x3e,0xa3,0x71,0xe0,0x30,0x20,0x31,0xbe,0x86,0xc4,0x1a,0x35,0x40,0x20,0x8d,0x89,0x28,0x5b,0xa0,0xd9,0xea,0x3d,0x44,0x42,0x87,0x83,0x48,0x36,0x49,0xe1,0xa0,0x75,0x67,0x8d,0x41,0x54,0x14,0x03,0xf5,0x2a,0x06,0x96,0x03,0x54,0xc4,0x14,0xd0,0x83,0x4a,0xfb,0x35,0x06,0x90,0x38,0x4e,0x46,0xb4,0x10,0xd9,0x81,0x49,0x72,0x40,0x01,0x0a,0x95,0xd4,0x36,0x20,0xd7,0x55,0x10,}; const uint8_t* const _I_RFIDDolphinSuccess_108x57[] = {_I_RFIDDolphinSuccess_108x57_0}; -const uint8_t _I_SDError_43x35_0[] = {0x01,0x00,0x6f,0x00,0xff,0x7f,0xc0,0x05,0x03,0x80,0x82,0x8e,0x08,0x05,0x59,0xe8,0x16,0x82,0x2d,0x30,0x8c,0x43,0x20,0xc0,0x51,0xb0,0x43,0x23,0x10,0x30,0x88,0xf0,0x20,0xdb,0x08,0x08,0x2c,0x70,0x10,0x3f,0x00,0x5c,0x80,0xa8,0x11,0x60,0xea,0x0a,0x54,0x8f,0xe5,0x99,0xfe,0x4f,0xc0,0xa6,0x70,0x10,0x89,0x60,0x23,0xff,0x91,0xa9,0x70,0x25,0xff,0x21,0xa9,0x70,0x2b,0xfe,0x42,0xc9,0x60,0x30,0x7e,0x40,0x89,0x42,0x30,0x12,0x08,0x80,0x14,0xa0,0x11,0x10,0x28,0xc0,0x66,0x10,0x08,0x74,0x30,0x8f,0xe0,0x58,0x5c,0x88,0x14,0xc0,0x43,0x01,0x8f,0x81,0x4f,0x05,0x20,0x02,0x9f,0xf3,0x80,0xcb,0x10,}; -const uint8_t* const _I_SDError_43x35[] = {_I_SDError_43x35_0}; - const uint8_t _I_SDQuestion_35x43_0[] = {0x01,0x00,0x67,0x00,0xf8,0x7f,0xc0,0x03,0x03,0xfc,0x01,0x0a,0x0f,0x38,0xa4,0xe4,0xa4,0x80,0x4f,0x0c,0x20,0x13,0xc0,0x9f,0x80,0x02,0x15,0xfe,0x00,0x04,0x29,0xfc,0x03,0xfd,0x07,0xfa,0x47,0xe7,0xdf,0xc8,0x3f,0xea,0x1f,0x7f,0xfc,0x41,0xff,0xb8,0xff,0xf8,0x10,0x7f,0xe0,0x4e,0xef,0x86,0x08,0x68,0x33,0xf1,0x10,0xff,0x3f,0xf1,0xf1,0x60,0x81,0x06,0x1e,0x36,0x10,0x20,0xe1,0xc0,0x87,0xc7,0x02,0x0f,0xd3,0xff,0xe3,0x02,0x0f,0xe8,0x08,0x7f,0xd0,0x21,0x89,0xc4,0x08,0x9f,0x70,0x21,0x9a,0x08,0x08,0xc1,0x89,0x02,0x20,0x62,0x40,0x8f,0xfe,0x68,0x98,}; const uint8_t* const _I_SDQuestion_35x43[] = {_I_SDQuestion_35x43_0}; @@ -609,9 +603,6 @@ const uint8_t* const _I_SDcardFail_11x8[] = {_I_SDcardFail_11x8_0}; const uint8_t _I_SDcardMounted_11x8_0[] = {0x01,0x00,0x09,0x00,0xff,0xc1,0xff,0xf0,0x40,0x1c,0xd9,0xe0,0x00,}; const uint8_t* const _I_SDcardMounted_11x8[] = {_I_SDcardMounted_11x8_0}; -const uint8_t _I_USBConnected_15x8_0[] = {0x00,0xF0,0x07,0x08,0x7C,0x04,0x44,0x07,0x54,0x07,0x54,0x04,0x44,0x08,0x7C,0xF0,0x07,}; -const uint8_t* const _I_USBConnected_15x8[] = {_I_USBConnected_15x8_0}; - const uint8_t _I_Lock_7x8_0[] = {0x00,0x1C,0x22,0x22,0x7F,0x7F,0x77,0x7F,0x3E,}; const uint8_t* const _I_Lock_7x8[] = {_I_Lock_7x8_0}; @@ -645,9 +636,6 @@ const uint8_t* const _I_Error_62x31[] = {_I_Error_62x31_0}; const uint8_t _I_Updating_32x40_0[] = {0x01,0x00,0x56,0x00,0xc0,0x7f,0xc0,0x03,0xc0,0x01,0x97,0x82,0x07,0x00,0xe0,0x5c,0x00,0x65,0x38,0x01,0x94,0x70,0x06,0x50,0xe0,0x19,0x41,0xc0,0x65,0xff,0x01,0xb4,0x0c,0x02,0x7e,0x08,0x38,0x0c,0x7c,0xd6,0x70,0x18,0xfb,0xfe,0xfc,0x0c,0x18,0xc8,0x78,0x20,0x33,0x81,0x8f,0x8a,0x07,0x3e,0xbe,0x70,0x38,0x71,0xff,0xc7,0x0f,0xc7,0x0f,0xf8,0x71,0xc0,0x76,0x13,0x30,0xd9,0x88,0xcc,0x5f,0x03,0xb2,0x21,0xa1,0x2c,0xc0,0x26,0x82,0x10,0x1f,0x80,0xd1,0x24,0x40,0x04,}; const uint8_t* const _I_Updating_32x40[] = {_I_Updating_32x40_0}; -const uint8_t _I_DolphinExcited_64x63_0[] = {0x01,0x00,0x36,0x01,0x00,0x25,0x00,0x0f,0xd2,0x00,0x3b,0xe0,0x00,0xeb,0x10,0x0c,0x34,0x40,0x30,0xd0,0x88,0x80,0x1d,0xa1,0x00,0x42,0xfc,0x7f,0xc0,0x63,0x04,0x01,0x0e,0x02,0x0f,0x00,0x00,0x8c,0x08,0x0e,0x37,0x00,0x10,0xc6,0x20,0x10,0x10,0xd9,0x11,0x92,0x1c,0x1a,0x3e,0x00,0x04,0x42,0x02,0x1a,0x20,0xb0,0xce,0x00,0x64,0x07,0x20,0x59,0x16,0x50,0x36,0x45,0x94,0x84,0x78,0x20,0x60,0x75,0x8e,0x43,0x06,0x63,0x3c,0x33,0x94,0x0c,0xd2,0x5c,0x30,0x38,0xe4,0x08,0x43,0x10,0xc0,0x5e,0x06,0x22,0x53,0x1a,0x02,0x08,0x7f,0xd0,0x32,0xc1,0x50,0x21,0x14,0x0e,0x70,0x1c,0x46,0xe2,0x07,0x19,0x06,0x3c,0xdc,0x20,0x91,0xae,0x01,0xcc,0xbe,0x30,0x09,0xfc,0x12,0x41,0xff,0x83,0xcc,0x0a,0xa3,0x1f,0x03,0x99,0xe8,0x7c,0x10,0xf8,0x25,0xa0,0x5e,0x50,0x0f,0x84,0x1e,0x09,0x54,0x03,0x9f,0xf2,0x07,0x02,0xd5,0x11,0xca,0x01,0xfe,0x80,0xc0,0xaa,0x9f,0xf0,0x39,0x5f,0xd0,0x43,0xaa,0x83,0x41,0x92,0xc3,0x1f,0x03,0x8d,0x52,0x02,0x2e,0x25,0xc9,0x6a,0x99,0x46,0xa6,0x2a,0xa0,0x1c,0xaf,0xca,0x62,0x94,0x28,0xcb,0x7e,0x0f,0x15,0x71,0xf8,0x3c,0x22,0x71,0x03,0x8a,0x84,0x67,0x18,0x0f,0xac,0x1c,0x0e,0x38,0x08,0x0c,0x3e,0x01,0xae,0xbd,0x13,0x0c,0x0e,0x35,0x8e,0xa8,0x1c,0xb0,0x1f,0xf8,0x06,0x83,0xf4,0x27,0x38,0x07,0xff,0xff,0x8f,0x03,0xa0,0x4c,0x80,0xed,0x60,0x03,0xb4,0x60,0x0e,0xd0,0x60,0x3a,0x87,0x84,0x0e,0xb7,0xc2,0xfa,0x18,0x05,0x44,0x20,0x73,0xff,0xf7,0xce,0xe4,0x07,0x2d,0x52,0x2c,0x80,0xe7,0x54,0xea,0x81,0xd7,0x50,0x0f,0x7a,0xaa,0x3d,0x41,0xe2,0x07,0x5a,0x80,0x3c,0xa0,0x40,0x72,0xd0,0x6a,0x80,0xa2,0x07,0x3a,0x05,0x54,0x8e,0x20,0x73,0xc0,0x03,0xd8,0x60,0x30,0x40,0x3a,0xc0,0x00,0xee,0xea,0x10,0x3b,0x80,}; -const uint8_t* const _I_DolphinExcited_64x63[] = {_I_DolphinExcited_64x63_0}; - const uint8_t _I_DolphinMafia_115x62_0[] = {0x01,0x00,0x21,0x02,0x00,0x1e,0x02,0x06,0x0e,0xcb,0x04,0x10,0x1d,0x91,0x88,0x40,0x3b,0x20,0xc0,0xec,0xc0,0x40,0x62,0x03,0xac,0x80,0x03,0xb2,0x31,0x00,0x90,0x03,0xae,0x5e,0x0e,0xcf,0xc4,0x56,0x01,0x40,0x07,0x56,0xbe,0x14,0x0e,0x2f,0xf1,0x5e,0x2a,0xa1,0xd1,0xc0,0x7c,0x3f,0xf0,0x70,0x73,0x70,0x35,0x41,0xd1,0xc0,0x7f,0xff,0xf0,0xf0,0x73,0x50,0x03,0xa4,0x0d,0x10,0x74,0x07,0x46,0x55,0xe0,0x07,0x10,0xb1,0xc3,0xa3,0x55,0xfe,0x03,0x88,0x94,0xe1,0xd1,0xd5,0x03,0x4a,0x3e,0x59,0x9e,0xaf,0xfe,0xff,0x05,0x60,0x4e,0xab,0xf5,0xff,0x95,0xb4,0xa4,0x3a,0x3f,0xd0,0xe0,0xfa,0x20,0x20,0xf8,0xd5,0xff,0xb5,0xf0,0x0f,0x88,0x3a,0x6a,0xbf,0xf8,0xaf,0x82,0x6f,0x03,0x07,0x47,0xaf,0xff,0x0a,0xfe,0x5f,0xc1,0xd3,0xf6,0xbf,0xe0,0x7f,0xfe,0xf0,0x73,0x41,0x00,0x43,0xfa,0xd7,0xf8,0x27,0xfe,0xe0,0x73,0x40,0x80,0x43,0xfe,0xab,0xfe,0x21,0xfc,0xe5,0x9b,0x05,0x48,0xea,0x3f,0xc8,0xfa,0xc4,0x66,0x07,0x44,0x0e,0x8f,0x00,0xb0,0x2b,0x31,0x07,0x0f,0x00,0x1c,0x72,0x00,0x70,0xf8,0x37,0xe5,0x81,0xff,0x89,0x08,0xf2,0x71,0x80,0x20,0xfe,0x2b,0xf0,0x5f,0xc0,0x38,0xc8,0xa5,0x60,0xc3,0x00,0xc7,0xf9,0xaf,0x81,0x2d,0x04,0x34,0x40,0xe1,0x98,0x47,0x68,0x04,0x92,0xab,0xc0,0x7e,0xb7,0xf7,0x39,0x03,0x85,0x8e,0x24,0xf1,0xc0,0x7f,0xf5,0x78,0x0f,0x53,0xb4,0xbc,0x1f,0xb8,0x1a,0x0c,0x61,0xc5,0x82,0xab,0xc0,0x3e,0xa3,0xa2,0xfc,0x07,0x46,0x09,0x60,0x19,0x8f,0x80,0xec,0x38,0x08,0x52,0x6c,0xb8,0xdc,0x28,0x7c,0x10,0x2a,0x5f,0x0f,0xfc,0x5a,0x01,0x05,0x1a,0x8e,0x02,0x02,0x1d,0x1f,0x81,0xa8,0xbe,0x13,0xf8,0x52,0x2c,0x8c,0x62,0x77,0x42,0x11,0x40,0xe0,0xca,0x93,0x8e,0x03,0x8a,0x30,0x10,0x48,0x54,0x03,0x04,0xbb,0x2c,0x00,0x0c,0x64,0x80,0xe4,0x0e,0x88,0x38,0x7c,0x10,0x04,0x09,0x48,0x83,0xac,0x1b,0x18,0xf3,0x44,0xc1,0xca,0x1d,0x15,0x40,0x8e,0x05,0x02,0x20,0xe6,0x24,0x12,0x8c,0x8b,0x05,0x21,0x07,0x24,0x14,0x08,0x73,0x80,0x19,0x78,0x43,0xb2,0xff,0x15,0x30,0xc4,0x01,0x26,0x8f,0x14,0x61,0xa9,0x8a,0x09,0x10,0x02,0x12,0x1c,0x80,0x84,0xaf,0x10,0x71,0xaa,0xc4,0x00,0x3b,0x04,0xea,0x24,0x48,0x1c,0xbd,0x8f,0xf8,0x00,0x67,0xf0,0x09,0x40,0x20,0x61,0x00,0xe4,0xf6,0x07,0x4b,0xc1,0x1f,0x07,0x14,0x40,0x1c,0x9d,0x66,0x79,0x24,0xc6,0xa0,0x0e,0x32,0x51,0xfa,0xce,0xe7,0x50,0x07,0x1c,0x80,0x30,0x58,0x0e,0xa2,0xcc,0xa0,0x19,0x00,0x71,0x42,0x13,0x27,0x40,0xf5,0x45,0x41,0xc5,0x08,0xb0,0x80,0xc6,0x18,0xf2,0x28,0x04,0x83,0xe8,0x58,0x10,0x30,0xc2,0x2c,0x40,0x91,0x89,0x3c,0x88,0x62,0x21,0xd2,0xff,0x03,0x87,0xc8,0x12,0x19,0x08,0x39,0x3e,0x83,0xb2,0x4a,0x0e,0xa2,0x0d,0xc0,0xe0,0x50,0x06,0xa7,0xe8,0x2c,0x94,0xc2,0x09,0x50,0x8c,0xce,0x20,0x34,0x70,0x71,0x41,0x3e,0x85,0xe2,0xe0,0x41,0x38,0x1e,0x28,0x3c,0x19,0xc8,0x70,0x4f,0xc1,0xdc,0xe0,0x74,0x01,0xd8,0xc6,0x24,0x00,0x82,0x81,0x7c,0x12,0xa6,0x7e,0x10,0x28,0xd8,0x22,0x00,0xe3,0xfc,0x34,0x53,0x00,0x23,0x1c,0x04,0x44,0x0e,0x50,0x10,0xeb,0x17,0xca,0x1c,0x07,0x20,}; const uint8_t* const _I_DolphinMafia_115x62[] = {_I_DolphinMafia_115x62_0}; @@ -657,9 +645,6 @@ const uint8_t* const _I_DolphinNice_96x59[] = {_I_DolphinNice_96x59_0}; const uint8_t _I_DolphinWait_61x59_0[] = {0x01,0x00,0x56,0x01,0x00,0x17,0xfa,0x1e,0x06,0x4f,0x84,0x06,0xe0,0x07,0x48,0x64,0x03,0x01,0x01,0x03,0x9c,0x0c,0x04,0x30,0x60,0x31,0x70,0x00,0x65,0x08,0x01,0x94,0xc0,0x06,0x51,0x00,0x5b,0x48,0x00,0x65,0x04,0x01,0x95,0x00,0x82,0xd8,0x00,0x19,0x40,0x7e,0x00,0x75,0x1f,0x88,0xe0,0x88,0x02,0x1a,0x1f,0x94,0x14,0x0e,0xbf,0x98,0x58,0x5c,0x42,0x45,0x00,0x9e,0x99,0x87,0x01,0x02,0x11,0x94,0xf2,0x2e,0x03,0x18,0x39,0x28,0x70,0x1f,0xc0,0x3e,0x42,0x00,0xe5,0x80,0xff,0xdf,0xc0,0xe5,0xf8,0x85,0xd8,0x10,0x27,0x40,0xf9,0xc2,0x63,0x88,0x12,0x82,0x6a,0x20,0x50,0x41,0xe9,0x42,0x20,0x95,0x48,0x6e,0x0c,0xfa,0x9a,0xaf,0xf9,0x90,0xe2,0x10,0x2e,0xac,0xe0,0x0e,0x98,0x29,0x52,0x11,0x13,0x23,0x15,0x3e,0x20,0x3c,0x61,0x40,0x52,0xfc,0x4f,0xe2,0x10,0x38,0x68,0x1c,0xa0,0xfc,0x08,0xbe,0x04,0x1e,0x5e,0x01,0xb9,0x03,0xc5,0x60,0x24,0xf2,0x84,0x60,0x63,0x40,0x71,0x27,0x9c,0x0e,0x2b,0x04,0x6c,0xa4,0x06,0x15,0x08,0x6c,0x99,0x8c,0xa6,0x0f,0x81,0x00,0x0c,0x08,0xf0,0x3c,0x05,0x61,0xc0,0x40,0x86,0xd0,0x30,0x78,0x80,0x0c,0xc6,0x2b,0x92,0x00,0x0d,0x51,0xf0,0x2d,0x42,0x0a,0x8e,0xaa,0x34,0x0f,0x4a,0x85,0x55,0x6e,0x20,0xf3,0xd5,0x6a,0x84,0xa2,0x66,0x2a,0x05,0xf7,0xaa,0x07,0x18,0xaf,0xfb,0x7f,0xea,0xc1,0xef,0xc0,0xe3,0xea,0x80,0xf8,0x27,0xf0,0x0a,0xc0,0x1c,0x67,0xa2,0xd1,0xb1,0xc0,0x34,0x00,0x71,0x14,0x8f,0x00,0x98,0x34,0x02,0x69,0xd0,0x37,0x90,0x16,0xf1,0x00,0x06,0xe1,0x84,0x31,0x89,0x14,0xe9,0xdc,0x40,0x38,0xa4,0xc4,0x4c,0x3c,0x1f,0x88,0x8c,0x5b,0xc3,0x01,0xbc,0x40,0x3f,0xf0,0xf6,0x71,0x0c,0x0b,0xe0,0x07,0x3c,0x0a,0xf8,0xa3,0xf0,0x03,0xb8,0xd8,0x80,0xe8,0x87,0x1b,0xa8,0x1c,0x78,0x1f,0xf8,0x0e,0x7e,0x01,0x6a,0x03,0x94,0x0f,0xfd,0xa0,0x80,0x7d,0x49,0x04,0x4d,0x12,0xc0,0xfa,0x83,0x83,0xbe,0x26,0x8d,0x02,0x05,0xd5,0xff,0xff,0xeb,0xe9,0x31,0x90,0x40,0x80,}; const uint8_t* const _I_DolphinWait_61x59[] = {_I_DolphinWait_61x59_0}; -const uint8_t _I_iButtonDolphinSuccess_109x60_0[] = {0x01,0x00,0xac,0x01,0x00,0x17,0xfe,0x1e,0x0c,0xaf,0x04,0x02,0xe0,0x0d,0xa8,0xf4,0x03,0x01,0x03,0x06,0x46,0x02,0x02,0x03,0x18,0xe0,0x36,0x2c,0x00,0x36,0x00,0x2c,0x40,0x3e,0x60,0xd8,0x84,0x01,0x0c,0x5a,0x40,0x05,0x82,0x01,0x0e,0x04,0x0d,0x70,0x42,0x04,0x90,0x49,0x02,0xe4,0x20,0x41,0x28,0xc0,0x07,0x40,0x06,0xf8,0x00,0xa4,0x00,0xd6,0x03,0xa8,0x37,0x44,0x2a,0x31,0x74,0xd3,0x83,0x57,0x80,0x0d,0xc7,0x18,0xa9,0xa8,0x36,0x2a,0x86,0x06,0x8d,0xfc,0x36,0x60,0xd7,0xc0,0x3b,0x8c,0x36,0xf0,0x4a,0x05,0xf9,0x6e,0x5e,0x06,0x23,0x41,0x24,0x1f,0xf6,0x01,0x74,0x01,0xb1,0xe3,0x82,0x81,0x47,0x40,0x0d,0x7c,0x87,0x8e,0x12,0x05,0x1a,0x84,0x0d,0xb6,0xa0,0xd2,0x85,0x86,0xc8,0x1a,0x50,0x40,0x69,0x40,0xb2,0x1f,0xf0,0x69,0x50,0x01,0xa5,0x08,0xfc,0x03,0x5f,0x60,0x0d,0x28,0x84,0x1a,0x07,0x18,0x06,0xaf,0x00,0x1a,0x3c,0x03,0xb8,0xc3,0x20,0xd0,0x28,0x87,0xfc,0x8a,0x50,0x08,0x78,0x08,0x70,0x77,0x0c,0x44,0x06,0x05,0x30,0xff,0x18,0x4a,0x01,0x30,0x01,0x0d,0x33,0x19,0x11,0x1b,0x8c,0xa2,0xf8,0x7d,0x27,0x71,0xd0,0x20,0x51,0x20,0x68,0xd5,0x00,0x42,0x0d,0x2c,0x00,0x08,0x64,0x10,0x19,0x20,0x28,0x75,0x07,0x53,0x3d,0x18,0x35,0x2a,0x9f,0xf4,0x9a,0x41,0x90,0x23,0x00,0x94,0x43,0xe0,0x5e,0xae,0x03,0x9d,0xb4,0xe0,0xd1,0x0d,0x8c,0xd0,0x52,0xb1,0x00,0xd9,0x83,0x46,0x34,0x45,0x41,0xa8,0x9f,0x86,0x01,0x14,0x05,0x08,0x08,0x81,0xa6,0x62,0x10,0x68,0xe5,0x20,0x70,0x41,0x80,0x80,0x10,0xc4,0x34,0x48,0x04,0x2a,0x38,0x0d,0x99,0x16,0x02,0x1a,0xd5,0x10,0x6c,0x5e,0x2e,0x0b,0xa1,0x4b,0x0a,0x60,0xc1,0xa7,0x84,0xfc,0x58,0x01,0xb5,0x02,0x82,0xb4,0xc4,0x16,0x22,0xa5,0x06,0x96,0x19,0x20,0x20,0xd7,0x30,0x8c,0x0f,0x08,0x05,0x10,0x68,0xa1,0x44,0x1a,0x98,0x08,0x14,0x11,0x28,0x21,0x91,0x1d,0x8f,0x83,0xfe,0x07,0x1b,0x00,0x34,0x61,0x00,0xd3,0x1d,0x8c,0x7a,0x01,0x7e,0x80,0x56,0x30,0x06,0xb1,0x4a,0x08,0xd4,0xbf,0xc1,0x31,0xc0,0x7f,0xe8,0xf0,0x08,0x3c,0x40,0x1a,0x80,0x04,0x5a,0x8c,0x10,0x80,0x40,0xd7,0x05,0x08,0x36,0xc0,0xe2,0x0d,0xb8,0x30,0x34,0x45,0x82,0x0d,0x72,0x49,0x03,0x5a,0x41,0x55,0xf8,0x7f,0xff,0xe8,0x72,0x06,0xae,0x03,0xf4,0x0c,0x1d,0xf8,0x18,0x60,0x40,0xd2,0x4b,0x9f,0xd0,0x1a,0x35,0x71,0x48,0xc0,0x95,0x42,0x0d,0x4d,0x50,0x70,0x75,0x40,0xd1,0x80,0x83,0x5a,0xa1,0x55,0x00,0x0c,0x05,0xa4,0x20,0xd2,}; -const uint8_t* const _I_iButtonDolphinSuccess_109x60[] = {_I_iButtonDolphinSuccess_109x60_0}; - const uint8_t _I_iButtonDolphinVerySuccess_108x52_0[] = {0x01,0x00,0xc2,0x01,0x00,0x0f,0xe2,0xfe,0x0d,0xb8,0x3e,0x02,0x06,0x0c,0x9f,0x00,0x08,0x61,0x80,0xd9,0x8c,0x00,0x86,0x60,0x0d,0x98,0x30,0x08,0x6a,0x00,0xd9,0x80,0x80,0x87,0x40,0x0c,0x8c,0x00,0x0c,0xa8,0x01,0x12,0x00,0x2d,0x00,0x22,0x70,0x20,0x6b,0xc8,0x02,0x26,0x62,0x88,0x80,0x6c,0xc9,0x24,0x0d,0x9a,0x07,0x17,0xfe,0x1d,0x68,0x40,0x6c,0xe7,0x48,0x04,0x28,0x10,0x34,0xe8,0x10,0xd1,0x11,0xc4,0x01,0xa5,0x04,0x06,0x96,0xa0,0xa6,0x24,0xc2,0x88,0x17,0x88,0x1a,0x7d,0x43,0x78,0x82,0x4a,0x40,0x03,0x20,0xb0,0xff,0x20,0x16,0xa3,0xb2,0x48,0x03,0xe4,0x0d,0x1f,0xfc,0x06,0x3a,0x0d,0x4a,0x00,0x34,0xf8,0x00,0xd1,0x37,0x0f,0x82,0x9e,0x95,0x58,0x17,0x83,0xff,0x81,0x1b,0x0f,0xf1,0xfe,0x71,0xe0,0x69,0x7c,0x3f,0xe0,0x82,0xff,0xcf,0xc0,0x85,0x61,0x80,0x43,0xb0,0x5f,0xa8,0x79,0xdc,0x81,0xa5,0x70,0xc0,0x68,0x3c,0x10,0x1a,0x17,0xd5,0x28,0x42,0xd1,0x8f,0x84,0x46,0x83,0xb0,0x8e,0x40,0x34,0x5f,0xa8,0x38,0x34,0x45,0xa2,0x0d,0x18,0x04,0x9b,0x50,0x03,0x1a,0x14,0x35,0x36,0x5f,0x8f,0xf8,0xb8,0xa4,0x19,0x40,0x18,0xe8,0xa0,0xca,0x22,0xfe,0x7f,0xc4,0x05,0x20,0xa5,0x80,0xc6,0x82,0xcb,0x3f,0xf3,0x44,0xfc,0x12,0x40,0x18,0xe8,0x51,0x82,0x52,0x28,0xfc,0x38,0x0a,0x3e,0x48,0x98,0x6c,0x8f,0x43,0x00,0xe0,0x63,0xe0,0x62,0xe2,0x91,0x90,0x0a,0x02,0x0d,0x2f,0x82,0x50,0x41,0xa3,0x80,0x90,0x41,0x04,0xc3,0x01,0xc0,0x83,0x46,0x71,0x30,0x06,0x95,0x82,0x21,0x02,0x6e,0x88,0x6c,0x43,0x83,0x1f,0x2f,0x88,0x34,0x62,0x00,0xd1,0x15,0x08,0x2c,0x60,0xcc,0x51,0x0f,0x08,0xcc,0x81,0xa2,0x12,0x10,0x68,0xc6,0x3f,0x06,0xc2,0x06,0x8e,0x02,0x16,0x41,0x20,0x10,0xf8,0x01,0x85,0x00,0x19,0x0d,0x82,0x18,0x07,0x20,0x81,0x00,0x0c,0x9c,0x31,0x08,0x42,0x74,0x81,0xab,0x80,0x03,0x0c,0x32,0x11,0x0b,0x06,0xb9,0xc0,0x43,0xa3,0x10,0x8b,0x83,0x5c,0xe0,0x20,0x81,0xc8,0x26,0x49,0x4c,0x40,0x02,0x86,0x0a,0xc5,0x22,0x32,0x50,0x6b,0x93,0x86,0xc0,0x0d,0x19,0x18,0x35,0x8c,0x84,0x79,0x1a,0x84,0x84,0x1a,0xdf,0xc2,0xe0,0x8a,0xc7,0x51,0x22,0x06,0xb5,0x5e,0x3f,0x00,0x77,0x0d,0x60,0x36,0xfa,0xa9,0xd7,0x00,0x08,0x3a,0xc9,0x02,0x48,0xc0,0x05,0x54,0xba,0x98,0x8a,0xa8,0xf1,0x20,0x6a,0x6a,0x3d,0x43,0x61,0x80,0x4a,0x81,0xaf,0x40,0xea,0x8d,0x86,0x01,0x56,0x06,0x93,0x60,0x80,0x05,0xea,0x01,0x94,0xac,0x1b,0x11,0x80,0x19,0x45,0x41,0x44,0x0d,0x58,0x33,0x18,0xa1,0x4f,0xf3,0x06,0x1f,0x01,0x76,0x58,0x00,0xd9,0x83,0x52,0x7c,0x11,0x38,0x51,0x40,0x80,}; const uint8_t* const _I_iButtonDolphinVerySuccess_108x52[] = {_I_iButtonDolphinVerySuccess_108x52_0}; @@ -672,11 +657,14 @@ const Icon A_Levelup1_128x64 = {.width=128,.height=64,.frame_count=11,.frame_rat const Icon A_Levelup2_128x64 = {.width=128,.height=64,.frame_count=11,.frame_rate=2,.frames=_A_Levelup2_128x64}; const Icon I_125_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_125_10px}; const Icon I_Nfc_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_Nfc_10px}; +const Icon I_back_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_back_10px}; const Icon I_badusb_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_badusb_10px}; const Icon I_ble_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_ble_10px}; const Icon I_dir_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_dir_10px}; const Icon I_ibutt_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_ibutt_10px}; const Icon I_ir_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_ir_10px}; +const Icon I_loading_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_loading_10px}; +const Icon I_music_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_music_10px}; const Icon I_sub1_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_sub1_10px}; const Icon I_u2f_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_u2f_10px}; const Icon I_unknown_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_unknown_10px}; @@ -720,16 +708,13 @@ const Icon I_DolphinFirstStart6_58x54 = {.width=58,.height=54,.frame_count=1,.fr const Icon I_DolphinFirstStart7_61x51 = {.width=61,.height=51,.frame_count=1,.frame_rate=0,.frames=_I_DolphinFirstStart7_61x51}; const Icon I_DolphinFirstStart8_56x51 = {.width=56,.height=51,.frame_count=1,.frame_rate=0,.frames=_I_DolphinFirstStart8_56x51}; const Icon I_DolphinOkay_41x43 = {.width=41,.height=43,.frame_count=1,.frame_rate=0,.frames=_I_DolphinOkay_41x43}; -const Icon I_Flipper_young_80x60 = {.width=80,.height=60,.frame_count=1,.frame_rate=0,.frames=_I_Flipper_young_80x60}; const Icon I_ArrowDownEmpty_14x15 = {.width=14,.height=15,.frame_count=1,.frame_rate=0,.frames=_I_ArrowDownEmpty_14x15}; const Icon I_ArrowDownFilled_14x15 = {.width=14,.height=15,.frame_count=1,.frame_rate=0,.frames=_I_ArrowDownFilled_14x15}; const Icon I_ArrowUpEmpty_14x15 = {.width=14,.height=15,.frame_count=1,.frame_rate=0,.frames=_I_ArrowUpEmpty_14x15}; const Icon I_ArrowUpFilled_14x15 = {.width=14,.height=15,.frame_count=1,.frame_rate=0,.frames=_I_ArrowUpFilled_14x15}; -const Icon I_Back_15x10 = {.width=15,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_Back_15x10}; const Icon I_DolphinReadingSuccess_59x63 = {.width=59,.height=63,.frame_count=1,.frame_rate=0,.frames=_I_DolphinReadingSuccess_59x63}; const Icon I_Down_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Down_25x27}; const Icon I_Down_hvr_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Down_hvr_25x27}; -const Icon I_Fill_marker_7x7 = {.width=7,.height=7,.frame_count=1,.frame_rate=0,.frames=_I_Fill_marker_7x7}; const Icon I_InfraredArrowDown_4x8 = {.width=8,.height=4,.frame_count=1,.frame_rate=0,.frames=_I_InfraredArrowDown_4x8}; const Icon I_InfraredArrowUp_4x8 = {.width=8,.height=4,.frame_count=1,.frame_rate=0,.frames=_I_InfraredArrowUp_4x8}; const Icon I_InfraredLearnShort_128x31 = {.width=128,.height=31,.frame_count=1,.frame_rate=0,.frames=_I_InfraredLearnShort_128x31}; @@ -746,7 +731,6 @@ const Icon I_Vol_down_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0 const Icon I_Vol_down_hvr_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Vol_down_hvr_25x27}; const Icon I_Vol_up_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Vol_up_25x27}; const Icon I_Vol_up_hvr_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Vol_up_hvr_25x27}; -const Icon I_Back3_45x8 = {.width=45,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_Back3_45x8}; const Icon I_DoorLeft_70x55 = {.width=70,.height=55,.frame_count=1,.frame_rate=0,.frames=_I_DoorLeft_70x55}; const Icon I_DoorLocked_10x56 = {.width=10,.height=56,.frame_count=1,.frame_rate=0,.frames=_I_DoorLocked_10x56}; const Icon I_DoorRight_70x55 = {.width=70,.height=55,.frame_count=1,.frame_rate=0,.frames=_I_DoorRight_70x55}; @@ -810,7 +794,6 @@ const Icon I_RFIDBigChip_37x36 = {.width=37,.height=36,.frame_count=1,.frame_rat const Icon I_RFIDDolphinReceive_97x61 = {.width=97,.height=61,.frame_count=1,.frame_rate=0,.frames=_I_RFIDDolphinReceive_97x61}; const Icon I_RFIDDolphinSend_97x61 = {.width=97,.height=61,.frame_count=1,.frame_rate=0,.frames=_I_RFIDDolphinSend_97x61}; const Icon I_RFIDDolphinSuccess_108x57 = {.width=108,.height=57,.frame_count=1,.frame_rate=0,.frames=_I_RFIDDolphinSuccess_108x57}; -const Icon I_SDError_43x35 = {.width=43,.height=35,.frame_count=1,.frame_rate=0,.frames=_I_SDError_43x35}; const Icon I_SDQuestion_35x43 = {.width=35,.height=43,.frame_count=1,.frame_rate=0,.frames=_I_SDQuestion_35x43}; const Icon I_Cry_dolph_55x52 = {.width=55,.height=52,.frame_count=1,.frame_rate=0,.frames=_I_Cry_dolph_55x52}; const Icon I_Attention_5x8 = {.width=5,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_Attention_5x8}; @@ -827,7 +810,6 @@ const Icon I_PlaceholderL_11x13 = {.width=11,.height=13,.frame_count=1,.frame_ra const Icon I_PlaceholderR_30x13 = {.width=30,.height=13,.frame_count=1,.frame_rate=0,.frames=_I_PlaceholderR_30x13}; const Icon I_SDcardFail_11x8 = {.width=11,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_SDcardFail_11x8}; const Icon I_SDcardMounted_11x8 = {.width=11,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_SDcardMounted_11x8}; -const Icon I_USBConnected_15x8 = {.width=15,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_USBConnected_15x8}; const Icon I_Lock_7x8 = {.width=7,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_Lock_7x8}; const Icon I_MHz_25x11 = {.width=25,.height=11,.frame_count=1,.frame_rate=0,.frames=_I_MHz_25x11}; const Icon I_Quest_7x8 = {.width=7,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_Quest_7x8}; @@ -839,11 +821,9 @@ const Icon I_Connected_62x31 = {.width=62,.height=31,.frame_count=1,.frame_rate= const Icon I_Drive_112x35 = {.width=112,.height=35,.frame_count=1,.frame_rate=0,.frames=_I_Drive_112x35}; const Icon I_Error_62x31 = {.width=62,.height=31,.frame_count=1,.frame_rate=0,.frames=_I_Error_62x31}; const Icon I_Updating_32x40 = {.width=32,.height=40,.frame_count=1,.frame_rate=0,.frames=_I_Updating_32x40}; -const Icon I_DolphinExcited_64x63 = {.width=64,.height=63,.frame_count=1,.frame_rate=0,.frames=_I_DolphinExcited_64x63}; const Icon I_DolphinMafia_115x62 = {.width=115,.height=62,.frame_count=1,.frame_rate=0,.frames=_I_DolphinMafia_115x62}; const Icon I_DolphinNice_96x59 = {.width=96,.height=59,.frame_count=1,.frame_rate=0,.frames=_I_DolphinNice_96x59}; const Icon I_DolphinWait_61x59 = {.width=61,.height=59,.frame_count=1,.frame_rate=0,.frames=_I_DolphinWait_61x59}; -const Icon I_iButtonDolphinSuccess_109x60 = {.width=109,.height=60,.frame_count=1,.frame_rate=0,.frames=_I_iButtonDolphinSuccess_109x60}; const Icon I_iButtonDolphinVerySuccess_108x52 = {.width=108,.height=52,.frame_count=1,.frame_rate=0,.frames=_I_iButtonDolphinVerySuccess_108x52}; const Icon I_iButtonKey_49x44 = {.width=49,.height=44,.frame_count=1,.frame_rate=0,.frames=_I_iButtonKey_49x44}; diff --git a/assets/compiled/assets_icons.h b/assets/compiled/assets_icons.h index 12ddb78e..45947880 100644 --- a/assets/compiled/assets_icons.h +++ b/assets/compiled/assets_icons.h @@ -7,11 +7,14 @@ extern const Icon A_Levelup1_128x64; extern const Icon A_Levelup2_128x64; extern const Icon I_125_10px; extern const Icon I_Nfc_10px; +extern const Icon I_back_10px; extern const Icon I_badusb_10px; extern const Icon I_ble_10px; extern const Icon I_dir_10px; extern const Icon I_ibutt_10px; extern const Icon I_ir_10px; +extern const Icon I_loading_10px; +extern const Icon I_music_10px; extern const Icon I_sub1_10px; extern const Icon I_u2f_10px; extern const Icon I_unknown_10px; @@ -55,16 +58,13 @@ extern const Icon I_DolphinFirstStart6_58x54; extern const Icon I_DolphinFirstStart7_61x51; extern const Icon I_DolphinFirstStart8_56x51; extern const Icon I_DolphinOkay_41x43; -extern const Icon I_Flipper_young_80x60; extern const Icon I_ArrowDownEmpty_14x15; extern const Icon I_ArrowDownFilled_14x15; extern const Icon I_ArrowUpEmpty_14x15; extern const Icon I_ArrowUpFilled_14x15; -extern const Icon I_Back_15x10; extern const Icon I_DolphinReadingSuccess_59x63; extern const Icon I_Down_25x27; extern const Icon I_Down_hvr_25x27; -extern const Icon I_Fill_marker_7x7; extern const Icon I_InfraredArrowDown_4x8; extern const Icon I_InfraredArrowUp_4x8; extern const Icon I_InfraredLearnShort_128x31; @@ -81,7 +81,6 @@ extern const Icon I_Vol_down_25x27; extern const Icon I_Vol_down_hvr_25x27; extern const Icon I_Vol_up_25x27; extern const Icon I_Vol_up_hvr_25x27; -extern const Icon I_Back3_45x8; extern const Icon I_DoorLeft_70x55; extern const Icon I_DoorLocked_10x56; extern const Icon I_DoorRight_70x55; @@ -145,7 +144,6 @@ extern const Icon I_RFIDBigChip_37x36; extern const Icon I_RFIDDolphinReceive_97x61; extern const Icon I_RFIDDolphinSend_97x61; extern const Icon I_RFIDDolphinSuccess_108x57; -extern const Icon I_SDError_43x35; extern const Icon I_SDQuestion_35x43; extern const Icon I_Cry_dolph_55x52; extern const Icon I_Attention_5x8; @@ -162,7 +160,6 @@ extern const Icon I_PlaceholderL_11x13; extern const Icon I_PlaceholderR_30x13; extern const Icon I_SDcardFail_11x8; extern const Icon I_SDcardMounted_11x8; -extern const Icon I_USBConnected_15x8; extern const Icon I_Lock_7x8; extern const Icon I_MHz_25x11; extern const Icon I_Quest_7x8; @@ -174,10 +171,8 @@ extern const Icon I_Connected_62x31; extern const Icon I_Drive_112x35; extern const Icon I_Error_62x31; extern const Icon I_Updating_32x40; -extern const Icon I_DolphinExcited_64x63; extern const Icon I_DolphinMafia_115x62; extern const Icon I_DolphinNice_96x59; extern const Icon I_DolphinWait_61x59; -extern const Icon I_iButtonDolphinSuccess_109x60; extern const Icon I_iButtonDolphinVerySuccess_108x52; extern const Icon I_iButtonKey_49x44; diff --git a/assets/icons/Archive/back_10px.png b/assets/icons/Archive/back_10px.png new file mode 100644 index 00000000..008efe82 Binary files /dev/null and b/assets/icons/Archive/back_10px.png differ diff --git a/assets/icons/Archive/loading_10px.png b/assets/icons/Archive/loading_10px.png new file mode 100644 index 00000000..9cc33b7f Binary files /dev/null and b/assets/icons/Archive/loading_10px.png differ diff --git a/assets/icons/Archive/music_10px.png b/assets/icons/Archive/music_10px.png new file mode 100644 index 00000000..d41eb0db Binary files /dev/null and b/assets/icons/Archive/music_10px.png differ diff --git a/assets/icons/Dolphin/Flipper_young_80x60.png b/assets/icons/Dolphin/Flipper_young_80x60.png deleted file mode 100644 index 4725aab9..00000000 Binary files a/assets/icons/Dolphin/Flipper_young_80x60.png and /dev/null differ diff --git a/assets/icons/Infrared/Back_15x10.png b/assets/icons/Infrared/Back_15x10.png deleted file mode 100644 index fe9ac5aa..00000000 Binary files a/assets/icons/Infrared/Back_15x10.png and /dev/null differ diff --git a/assets/icons/Infrared/Fill-marker_7x7.png b/assets/icons/Infrared/Fill-marker_7x7.png deleted file mode 100644 index afae1d04..00000000 Binary files a/assets/icons/Infrared/Fill-marker_7x7.png and /dev/null differ diff --git a/assets/icons/Interface/Back3_45x8.png b/assets/icons/Interface/Back3_45x8.png deleted file mode 100644 index 6cb945f6..00000000 Binary files a/assets/icons/Interface/Back3_45x8.png and /dev/null differ diff --git a/assets/icons/SDCard/SDError_43x35.png b/assets/icons/SDCard/SDError_43x35.png deleted file mode 100644 index d22e9853..00000000 Binary files a/assets/icons/SDCard/SDError_43x35.png and /dev/null differ diff --git a/assets/icons/StatusBar/USBConnected_15x8.png b/assets/icons/StatusBar/USBConnected_15x8.png deleted file mode 100644 index 4a282391..00000000 Binary files a/assets/icons/StatusBar/USBConnected_15x8.png and /dev/null differ diff --git a/assets/icons/iButton/DolphinExcited_64x63.png b/assets/icons/iButton/DolphinExcited_64x63.png deleted file mode 100644 index e695c85f..00000000 Binary files a/assets/icons/iButton/DolphinExcited_64x63.png and /dev/null differ diff --git a/assets/icons/iButton/iButtonDolphinSuccess_109x60.png b/assets/icons/iButton/iButtonDolphinSuccess_109x60.png deleted file mode 100644 index f234aba9..00000000 Binary files a/assets/icons/iButton/iButtonDolphinSuccess_109x60.png and /dev/null differ diff --git a/assets/resources/Manifest b/assets/resources/Manifest index 08d95044..9d854b13 100644 --- a/assets/resources/Manifest +++ b/assets/resources/Manifest @@ -1,5 +1,5 @@ V:0 -T:1651524332 +T:1654009290 D:badusb D:dolphin D:infrared @@ -226,7 +226,7 @@ D:infrared/assets F:d895fda2f48c6cc4c55e8a398ff52e43:74300:infrared/assets/tv.ir F:a157a80f5a668700403d870c23b9567d:470:music_player/Marble_Machine.fmf D:nfc/assets -F:c6826a621d081d68309e4be424d3d974:4715:nfc/assets/aid.nfc +F:81dc04c7b181f94b644079a71476dff4:4742:nfc/assets/aid.nfc F:86efbebdf41bb6bf15cc51ef88f069d5:2565:nfc/assets/country_code.nfc F:41b4f08774249014cb8d3dffa5f5c07d:1757:nfc/assets/currency_code.nfc F:c60e862919731b0bd538a1001bbc1098:17453:nfc/assets/mf_classic_dict.nfc @@ -235,8 +235,7 @@ F:dda1ef895b8a25fde57c874feaaef997:650:subghz/assets/came_atomo F:610a0ffa2479a874f2060eb2348104c5:2712:subghz/assets/keeloq_mfcodes F:9214f9c10463b746a27e82ce0b96e040:465:subghz/assets/keeloq_mfcodes_user F:653bd8d349055a41e1152e557d4a52d3:202:subghz/assets/nice_flor_s -F:00e967e5c558e44a0651bb821d5cf1d0:414:subghz/assets/setting_frequency_analyzer_user -F:16e8c7cb4a13f26ea55b2b0a59f9cc7a:554:subghz/assets/setting_user +F:c6ec4374275cd20f482ecd46de9f53e3:528:subghz/assets/setting_user D:u2f/assets F:7e11e688e39034bbb9d88410044795e1:365:u2f/assets/cert.der F:f60b88c20ed479ed9684e249f7134618:264:u2f/assets/cert_key.u2f diff --git a/assets/resources/nfc/assets/aid.nfc b/assets/resources/nfc/assets/aid.nfc index 43854f3d..30c1030e 100644 --- a/assets/resources/nfc/assets/aid.nfc +++ b/assets/resources/nfc/assets/aid.nfc @@ -64,6 +64,7 @@ A0000004540010: Etranzact Genesis Card A0000004540011: Etranzact Genesis Card 2 A0000004766C: GOOGLE_PAYMENT A0000005241010: RuPay +A0000006472F0001: FIDO U2F A0000006723010: TROY chip credit card A0000006723020: TROY chip debit card A0000007705850: XTRAPOWER diff --git a/assets/resources/subghz/assets/setting_frequency_analyzer_user b/assets/resources/subghz/assets/setting_frequency_analyzer_user deleted file mode 100644 index 2c03a402..00000000 --- a/assets/resources/subghz/assets/setting_frequency_analyzer_user +++ /dev/null @@ -1,19 +0,0 @@ -Filetype: Flipper SubGhz Setting File -Version: 1 -Frequency_default: 433920000 -Frequency: 300000000 -Frequency: 303875000 -Frequency: 304250000 -Frequency: 315000000 -Frequency: 318000000 -Frequency: 390000000 -Frequency: 418000000 -Frequency: 433075000 -Frequency: 433420000 -Frequency: 433920000 -Frequency: 434420000 -Frequency: 434775000 -Frequency: 438900000 -Frequency: 868350000 -Frequency: 915000000 -Frequency: 925000000 diff --git a/assets/resources/subghz/assets/setting_user b/assets/resources/subghz/assets/setting_user index 11bd984d..413dbf31 100644 --- a/assets/resources/subghz/assets/setting_user +++ b/assets/resources/subghz/assets/setting_user @@ -1,24 +1,18 @@ Filetype: Flipper SubGhz Setting File Version: 1 -Frequency_default: 433920000 -Frequency: 300000000 -Frequency: 303875000 -Frequency: 304250000 -Frequency: 315000000 -Frequency: 318000000 -Frequency: 390000000 -Frequency: 418000000 -Frequency: 433075000 -Frequency: 433420000 -Frequency: 433920000 -Frequency: 434420000 -Frequency: 434775000 -Frequency: 438900000 -Frequency: 868350000 -Frequency: 915000000 -Frequency: 925000000 -Hopper_frequency: 315000000 -Hopper_frequency: 318000000 -Hopper_frequency: 390000000 -Hopper_frequency: 433920000 -Hopper_frequency: 868350000 + +# Add Standard frequencies for your region +#add_standard_frequencies: true + +# Default Frequency: used as default for "Read" and "Read Raw" +#default_frequency: 433920000 + +# Frequencies used for "Read", "Read Raw" and "Frequency Analyzer" +#frequency: 300000000 +#frequency: 310000000 +#frequency: 320000000 + +# Frequencies used for hopping mode (keep this list small or flipper will miss signal) +#hopper_frequency: 300000000 +#hopper_frequency: 310000000 +#hopper_frequency: 310000000 diff --git a/assets/unit_tests/subghz/firely.sub b/assets/unit_tests/subghz/linear.sub similarity index 88% rename from assets/unit_tests/subghz/firely.sub rename to assets/unit_tests/subghz/linear.sub index a38c21df..19ab5d72 100644 --- a/assets/unit_tests/subghz/firely.sub +++ b/assets/unit_tests/subghz/linear.sub @@ -2,6 +2,6 @@ Filetype: Flipper SubGhz Key File Version: 1 Frequency: 300000000 Preset: FuriHalSubGhzPresetOok650Async -Protocol: Firefly +Protocol: Linear Bit: 10 Key: 00 00 00 00 00 00 01 E4 diff --git a/assets/unit_tests/subghz/firefly_raw.sub b/assets/unit_tests/subghz/linear_raw.sub similarity index 100% rename from assets/unit_tests/subghz/firefly_raw.sub rename to assets/unit_tests/subghz/linear_raw.sub diff --git a/assets/unit_tests/subghz/security_pls_1_0.sub b/assets/unit_tests/subghz/security_pls_1_0.sub new file mode 100644 index 00000000..f0909a77 --- /dev/null +++ b/assets/unit_tests/subghz/security_pls_1_0.sub @@ -0,0 +1,7 @@ +Filetype: Flipper SubGhz Key File +Version: 1 +Frequency: 310000000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: Security+ 1.0 +Bit: 42 +Key: 1C 41 D2 39 E6 A3 8B CC diff --git a/assets/unit_tests/subghz/security_pls_2_0.sub b/assets/unit_tests/subghz/security_pls_2_0.sub new file mode 100644 index 00000000..a13ab0fc --- /dev/null +++ b/assets/unit_tests/subghz/security_pls_2_0.sub @@ -0,0 +1,8 @@ +Filetype: Flipper SubGhz Key File +Version: 1 +Frequency: 315000000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: Security+ 2.0 +Bit: 62 +Key: 00 00 3D 29 0F B9 BE EE +Secplus_packet_1: 00 00 3C 01 6B 19 DD 60 diff --git a/core/furi/common_defines.h b/core/furi/common_defines.h index 5b0e6971..5e9a7904 100644 --- a/core/furi/common_defines.h +++ b/core/furi/common_defines.h @@ -84,7 +84,7 @@ extern "C" { #endif #ifndef FURI_BIT -#define FURI_BIT(x, n) ((x) >> (n)&1) +#define FURI_BIT(x, n) (((x) >> (n)) & 1) #endif #ifndef FURI_IS_IRQ_MASKED diff --git a/firmware/targets/f7/ble_glue/app_conf.h b/firmware/targets/f7/ble_glue/app_conf.h index 1406f964..1d7474da 100644 --- a/firmware/targets/f7/ble_glue/app_conf.h +++ b/firmware/targets/f7/ble_glue/app_conf.h @@ -127,7 +127,7 @@ * Maximum number of simultaneous connections that the device will support. * Valid values are from 1 to 8 */ -#define CFG_BLE_NUM_LINK 2 +#define CFG_BLE_NUM_LINK 1 /** * Maximum number of Services that can be stored in the GATT database. diff --git a/firmware/targets/f7/ble_glue/battery_service.c b/firmware/targets/f7/ble_glue/battery_service.c index 85f1eeea..a95f9187 100644 --- a/firmware/targets/f7/ble_glue/battery_service.c +++ b/firmware/targets/f7/ble_glue/battery_service.c @@ -3,18 +3,50 @@ #include "ble.h" #include +#include #define TAG "BtBatterySvc" typedef struct { uint16_t svc_handle; - uint16_t char_level_handle; + uint16_t battery_level_char_handle; + uint16_t power_state_char_handle; } BatterySvc; +enum { + // Common states + BatterySvcPowerStateUnknown = 0b00, + BatterySvcPowerStateUnsupported = 0b01, + // Level states + BatterySvcPowerStateGoodLevel = 0b10, + BatterySvcPowerStateCriticallyLowLevel = 0b11, + // Charging states + BatterySvcPowerStateNotCharging = 0b10, + BatterySvcPowerStateCharging = 0b11, + // Discharging states + BatterySvcPowerStateNotDischarging = 0b10, + BatterySvcPowerStateDischarging = 0b11, + // Battery states + BatterySvcPowerStateBatteryNotPresent = 0b10, + BatterySvcPowerStateBatteryPresent = 0b11, +}; + +typedef struct { + uint8_t present : 2; + uint8_t discharging : 2; + uint8_t charging : 2; + uint8_t level : 2; +} BattrySvcPowerState; + +_Static_assert(sizeof(BattrySvcPowerState) == 1, "Incorrect structure size"); + static BatterySvc* battery_svc = NULL; +#define BATTERY_POWER_STATE (0x2A1A) + static const uint16_t service_uuid = BATTERY_SERVICE_UUID; -static const uint16_t char_battery_level_uuid = BATTERY_LEVEL_CHAR_UUID; +static const uint16_t battery_level_char_uuid = BATTERY_LEVEL_CHAR_UUID; +static const uint16_t power_state_char_uuid = BATTERY_POWER_STATE; void battery_svc_start() { battery_svc = malloc(sizeof(BatterySvc)); @@ -22,7 +54,7 @@ void battery_svc_start() { // Add Battery service status = aci_gatt_add_service( - UUID_TYPE_16, (Service_UUID_t*)&service_uuid, PRIMARY_SERVICE, 4, &battery_svc->svc_handle); + UUID_TYPE_16, (Service_UUID_t*)&service_uuid, PRIMARY_SERVICE, 8, &battery_svc->svc_handle); if(status) { FURI_LOG_E(TAG, "Failed to add Battery service: %d", status); } @@ -30,24 +62,47 @@ void battery_svc_start() { status = aci_gatt_add_char( battery_svc->svc_handle, UUID_TYPE_16, - (Char_UUID_t*)&char_battery_level_uuid, + (Char_UUID_t*)&battery_level_char_uuid, 1, CHAR_PROP_READ | CHAR_PROP_NOTIFY, ATTR_PERMISSION_AUTHEN_READ, GATT_DONT_NOTIFY_EVENTS, 10, CHAR_VALUE_LEN_CONSTANT, - &battery_svc->char_level_handle); + &battery_svc->battery_level_char_handle); if(status) { FURI_LOG_E(TAG, "Failed to add Battery level characteristic: %d", status); } + // Add Power state characteristic + status = aci_gatt_add_char( + battery_svc->svc_handle, + UUID_TYPE_16, + (Char_UUID_t*)&power_state_char_uuid, + 1, + CHAR_PROP_READ | CHAR_PROP_NOTIFY, + ATTR_PERMISSION_AUTHEN_READ, + GATT_DONT_NOTIFY_EVENTS, + 10, + CHAR_VALUE_LEN_CONSTANT, + &battery_svc->power_state_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to add Battery level characteristic: %d", status); + } + // Update power state charachteristic + battery_svc_update_power_state(); } void battery_svc_stop() { tBleStatus status; if(battery_svc) { // Delete Battery level characteristic - status = aci_gatt_del_char(battery_svc->svc_handle, battery_svc->char_level_handle); + status = + aci_gatt_del_char(battery_svc->svc_handle, battery_svc->battery_level_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to delete Battery level characteristic: %d", status); + } + // Delete Power state characteristic + status = aci_gatt_del_char(battery_svc->svc_handle, battery_svc->power_state_char_handle); if(status) { FURI_LOG_E(TAG, "Failed to delete Battery level characteristic: %d", status); } @@ -73,9 +128,39 @@ bool battery_svc_update_level(uint8_t battery_charge) { // Update battery level characteristic FURI_LOG_D(TAG, "Updating battery level characteristic"); tBleStatus result = aci_gatt_update_char_value( - battery_svc->svc_handle, battery_svc->char_level_handle, 0, 1, &battery_charge); + battery_svc->svc_handle, battery_svc->battery_level_char_handle, 0, 1, &battery_charge); if(result) { FURI_LOG_E(TAG, "Failed updating RX characteristic: %d", result); } return result != BLE_STATUS_SUCCESS; } + +bool battery_svc_update_power_state() { + // Check if service was started + if(battery_svc == NULL) { + return false; + } + // Update power state characteristic + BattrySvcPowerState power_state = { + .level = BatterySvcPowerStateUnsupported, + .present = BatterySvcPowerStateBatteryPresent, + }; + if(furi_hal_power_is_charging()) { + power_state.charging = BatterySvcPowerStateCharging; + power_state.discharging = BatterySvcPowerStateNotDischarging; + } else { + power_state.charging = BatterySvcPowerStateNotCharging; + power_state.discharging = BatterySvcPowerStateDischarging; + } + FURI_LOG_D(TAG, "Updating power state characteristic"); + tBleStatus result = aci_gatt_update_char_value( + battery_svc->svc_handle, + battery_svc->power_state_char_handle, + 0, + 1, + (uint8_t*)&power_state); + if(result) { + FURI_LOG_E(TAG, "Failed updating Power state characteristic: %d", result); + } + return result != BLE_STATUS_SUCCESS; +} diff --git a/firmware/targets/f7/ble_glue/battery_service.h b/firmware/targets/f7/ble_glue/battery_service.h index 2d35e252..f38bfc00 100644 --- a/firmware/targets/f7/ble_glue/battery_service.h +++ b/firmware/targets/f7/ble_glue/battery_service.h @@ -15,6 +15,8 @@ bool battery_svc_is_started(); bool battery_svc_update_level(uint8_t battery_level); +bool battery_svc_update_power_state(); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt.c b/firmware/targets/f7/furi_hal/furi_hal_bt.c index 48d69844..1e869077 100755 --- a/firmware/targets/f7/furi_hal/furi_hal_bt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt.c @@ -284,6 +284,12 @@ void furi_hal_bt_update_battery_level(uint8_t battery_level) { } } +void furi_hal_bt_update_power_state() { + if(battery_svc_is_started()) { + battery_svc_update_power_state(); + } +} + void furi_hal_bt_get_key_storage_buff(uint8_t** key_buff_addr, uint16_t* key_buff_size) { ble_app_get_key_storage_buff(key_buff_addr, key_buff_size); } diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.c b/firmware/targets/f7/furi_hal/furi_hal_nfc.c old mode 100755 new mode 100644 index 768a4bac..00c96ec3 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc.c @@ -1,9 +1,13 @@ +#include #include "furi_hal_nfc.h" #include +#include #include #include #include -#include + +#include +#include #define TAG "FuriHalNfc" @@ -57,6 +61,7 @@ bool furi_hal_nfc_detect(FuriHalNfcDevData* nfc_data, uint32_t timeout) { rfalLowPowerModeStop(); rfalNfcState state = rfalNfcGetState(); + rfalNfcState state_old = 0; if(state == RFAL_NFC_STATE_NOTINIT) { rfalNfcInitialize(); } @@ -79,11 +84,14 @@ bool furi_hal_nfc_detect(FuriHalNfcDevData* nfc_data, uint32_t timeout) { while(true) { rfalNfcWorker(); state = rfalNfcGetState(); + if(state != state_old) { + FURI_LOG_T(TAG, "State change %d -> %d", state_old, state); + } + state_old = state; if(state == RFAL_NFC_STATE_ACTIVATED) { detected = true; break; } - FURI_LOG_T(TAG, "Current state %d", state); if(state == RFAL_NFC_STATE_POLL_ACTIVATION) { start = DWT->CYCCNT; continue; @@ -334,6 +342,8 @@ bool furi_hal_nfc_emulate_nfca( break; } if(buff_tx_len) { + if(buff_tx_len == UINT16_MAX) buff_tx_len = 0; + ReturnCode ret = rfalTransceiveBitsBlockingTx( buff_tx, buff_tx_len, @@ -394,6 +404,80 @@ ReturnCode furi_hal_nfc_data_exchange( return ret; } +static bool furi_hal_nfc_transparent_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) { + furi_assert(tx_rx->nfca_signal); + + platformDisableIrqCallback(); + + bool ret = false; + + // Start transparent mode + st25r3916ExecuteCommand(ST25R3916_CMD_TRANSPARENT_MODE); + // Reconfigure gpio + furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_nfc); + furi_hal_gpio_init(&gpio_spi_r_sck, GpioModeInput, GpioPullUp, GpioSpeedLow); + furi_hal_gpio_init(&gpio_spi_r_miso, GpioModeInput, GpioPullUp, GpioSpeedLow); + furi_hal_gpio_init(&gpio_nfc_cs, GpioModeInput, GpioPullUp, GpioSpeedLow); + furi_hal_gpio_init(&gpio_spi_r_mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_write(&gpio_spi_r_mosi, false); + + // Send signal + nfca_signal_encode(tx_rx->nfca_signal, tx_rx->tx_data, tx_rx->tx_bits, tx_rx->tx_parity); + digital_signal_send(tx_rx->nfca_signal->tx_signal, &gpio_spi_r_mosi); + furi_hal_gpio_write(&gpio_spi_r_mosi, false); + + // Configure gpio back to SPI and exit transparent + furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_nfc); + st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA); + + // Manually wait for interrupt + furi_hal_gpio_init(&gpio_rfid_pull, GpioModeInput, GpioPullDown, GpioSpeedVeryHigh); + st25r3916ClearAndEnableInterrupts(ST25R3916_IRQ_MASK_RXE); + + uint32_t irq = 0; + uint8_t rxe = 0; + uint32_t start = DWT->CYCCNT; + while(true) { + if(furi_hal_gpio_read(&gpio_rfid_pull) == true) { + st25r3916ReadRegister(ST25R3916_REG_IRQ_MAIN, &rxe); + if(rxe & (1 << 4)) { + irq = 1; + break; + } + } + uint32_t timeout = DWT->CYCCNT - start; + if(timeout / furi_hal_delay_instructions_per_microsecond() > timeout_ms * 1000) { + FURI_LOG_D(TAG, "Interrupt waiting timeout"); + break; + } + } + if(irq) { + uint8_t fifo_stat[2]; + st25r3916ReadMultipleRegisters( + ST25R3916_REG_FIFO_STATUS1, fifo_stat, ST25R3916_FIFO_STATUS_LEN); + uint16_t len = + ((((uint16_t)fifo_stat[1] & ST25R3916_REG_FIFO_STATUS2_fifo_b_mask) >> + ST25R3916_REG_FIFO_STATUS2_fifo_b_shift) + << RFAL_BITS_IN_BYTE); + len |= (((uint16_t)fifo_stat[0]) & 0x00FFU); + uint8_t rx[100]; + st25r3916ReadFifo(rx, len); + + tx_rx->rx_bits = len * 8; + memcpy(tx_rx->rx_data, rx, len); + + ret = true; + } else { + FURI_LOG_E(TAG, "Timeout error"); + ret = false; + } + + st25r3916ClearInterrupts(); + platformEnableIrqCallback(); + + return ret; +} + static uint32_t furi_hal_nfc_tx_rx_get_flag(FuriHalNfcTxRxType type) { uint32_t flags = 0; @@ -405,6 +489,9 @@ static uint32_t furi_hal_nfc_tx_rx_get_flag(FuriHalNfcTxRxType type) { } else if(type == FuriHalNfcTxRxTypeRaw) { flags = RFAL_TXRX_FLAGS_CRC_TX_MANUAL | RFAL_TXRX_FLAGS_CRC_RX_KEEP | RFAL_TXRX_FLAGS_PAR_RX_KEEP | RFAL_TXRX_FLAGS_PAR_TX_NONE; + } else if(type == FuriHalNfcTxRxTypeRxRaw) { + flags = RFAL_TXRX_FLAGS_CRC_TX_MANUAL | RFAL_TXRX_FLAGS_CRC_RX_KEEP | + RFAL_TXRX_FLAGS_PAR_RX_KEEP | RFAL_TXRX_FLAGS_PAR_TX_NONE; } return flags; @@ -470,6 +557,10 @@ bool furi_hal_nfc_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) { uint8_t* temp_rx_buff = NULL; uint16_t* temp_rx_bits = NULL; + if(tx_rx->tx_rx_type == FuriHalNfcTxRxTransparent) { + return furi_hal_nfc_transparent_tx_rx(tx_rx, timeout_ms); + } + // Prepare data for FIFO if necessary uint32_t flags = furi_hal_nfc_tx_rx_get_flag(tx_rx->tx_rx_type); if(tx_rx->tx_rx_type == FuriHalNfcTxRxTypeRaw) { @@ -502,7 +593,8 @@ bool furi_hal_nfc_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) { osDelay(1); } - if(tx_rx->tx_rx_type == FuriHalNfcTxRxTypeRaw) { + if(tx_rx->tx_rx_type == FuriHalNfcTxRxTypeRaw || + tx_rx->tx_rx_type == FuriHalNfcTxRxTypeRxRaw) { tx_rx->rx_bits = 8 * furi_hal_nfc_bitstream_to_data_and_parity( temp_rx_buff, *temp_rx_bits, tx_rx->rx_data, tx_rx->rx_parity); } else { diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.c b/firmware/targets/f7/furi_hal/furi_hal_resources.c index 4209e0cf..0af33cac 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.c @@ -68,7 +68,7 @@ const InputPin input_pins[] = { {.gpio = &gpio_button_down, .key = InputKeyDown, .inverted = true, .name = "Down"}, {.gpio = &gpio_button_right, .key = InputKeyRight, .inverted = true, .name = "Right"}, {.gpio = &gpio_button_left, .key = InputKeyLeft, .inverted = true, .name = "Left"}, - {.gpio = &gpio_button_ok, .key = InputKeyOk, .inverted = false, .name = "Ok"}, + {.gpio = &gpio_button_ok, .key = InputKeyOk, .inverted = false, .name = "OK"}, {.gpio = &gpio_button_back, .key = InputKeyBack, .inverted = true, .name = "Back"}, }; diff --git a/firmware/targets/f7/furi_hal/furi_hal_rtc.c b/firmware/targets/f7/furi_hal/furi_hal_rtc.c index b580a04b..df410a9f 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_rtc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_rtc.c @@ -33,6 +33,20 @@ typedef struct { _Static_assert(sizeof(DeveloperReg) == 4, "DeveloperReg size mismatch"); +#define FURI_HAL_RTC_SECONDS_PER_MINUTE 60 +#define FURI_HAL_RTC_SECONDS_PER_HOUR (FURI_HAL_RTC_SECONDS_PER_MINUTE * 60) +#define FURI_HAL_RTC_SECONDS_PER_DAY (FURI_HAL_RTC_SECONDS_PER_HOUR * 24) +#define FURI_HAL_RTC_MONTHS_COUNT 12 +#define FURI_HAL_RTC_EPOCH_START_YEAR 1970 +#define FURI_HAL_RTC_IS_LEAP_YEAR(year) \ + ((((year) % 4 == 0) && ((year) % 100 != 0)) || ((year) % 400 == 0)) + +static const uint8_t furi_hal_rtc_days_per_month[][FURI_HAL_RTC_MONTHS_COUNT] = { + {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, + {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}}; + +static const uint16_t furi_hal_rtc_days_per_year[] = {365, 366}; + void furi_hal_rtc_init_early() { // LSE and RTC LL_PWR_EnableBkUpAccess(); @@ -259,3 +273,34 @@ void furi_hal_rtc_set_pin_fails(uint32_t value) { uint32_t furi_hal_rtc_get_pin_fails() { return furi_hal_rtc_get_register(FuriHalRtcRegisterPinFails); } + +uint32_t furi_hal_rtc_datetime_to_timestamp(FuriHalRtcDateTime* datetime) { + uint32_t timestamp = 0; + uint8_t years = 0; + uint8_t leap_years = 0; + + for(uint16_t y = FURI_HAL_RTC_EPOCH_START_YEAR; y < datetime->year; y++) { + if(FURI_HAL_RTC_IS_LEAP_YEAR(y)) { + leap_years++; + } else { + years++; + } + } + + timestamp += + ((years * furi_hal_rtc_days_per_year[0]) + (leap_years * furi_hal_rtc_days_per_year[1])) * + FURI_HAL_RTC_SECONDS_PER_DAY; + + uint8_t year_index = (FURI_HAL_RTC_IS_LEAP_YEAR(datetime->year)) ? 1 : 0; + + for(uint8_t m = 0; m < (datetime->month - 1); m++) { + timestamp += furi_hal_rtc_days_per_month[year_index][m] * FURI_HAL_RTC_SECONDS_PER_DAY; + } + + timestamp += (datetime->day - 1) * FURI_HAL_RTC_SECONDS_PER_DAY; + timestamp += datetime->hour * FURI_HAL_RTC_SECONDS_PER_HOUR; + timestamp += datetime->minute * FURI_HAL_RTC_SECONDS_PER_MINUTE; + timestamp += datetime->second; + + return timestamp; +} diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.c b/firmware/targets/f7/furi_hal/furi_hal_subghz.c index 7f40a152..79262ef5 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.c +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.c @@ -412,7 +412,7 @@ void furi_hal_subghz_load_preset(FuriHalSubGhzPreset preset) { furi_hal_subghz_load_registers(furi_hal_subghz_preset_gfsk_9_99kb_async_regs); furi_hal_subghz_load_patable(furi_hal_subghz_preset_gfsk_async_patable); } else { - furi_crash("SugGhz: Missing config."); + furi_crash("SubGhz: Missing config."); } furi_hal_subghz_preset = preset; } @@ -564,7 +564,7 @@ uint32_t furi_hal_subghz_set_frequency_and_path(uint32_t value) { } else if(value >= 778999847 && value <= 928000000) { furi_hal_subghz_set_path(FuriHalSubGhzPath868); } else { - furi_crash("SugGhz: Incorrect frequency during set."); + furi_crash("SubGhz: Incorrect frequency during set."); } return value; } diff --git a/firmware/targets/furi_hal_include/furi_hal_bt.h b/firmware/targets/furi_hal_include/furi_hal_bt.h index 73b16bec..3a05081b 100644 --- a/firmware/targets/furi_hal_include/furi_hal_bt.h +++ b/firmware/targets/furi_hal_include/furi_hal_bt.h @@ -91,6 +91,9 @@ bool furi_hal_bt_change_app(FuriHalBtProfile profile, GapEventCallback event_cb, */ void furi_hal_bt_update_battery_level(uint8_t battery_level); +/** Update battery power state */ +void furi_hal_bt_update_power_state(); + /** Checks if BLE state is active * * @return true if device is connected or advertising, false otherwise diff --git a/firmware/targets/furi_hal_include/furi_hal_nfc.h b/firmware/targets/furi_hal_include/furi_hal_nfc.h index 20a46900..30672fb9 100755 --- a/firmware/targets/furi_hal_include/furi_hal_nfc.h +++ b/firmware/targets/furi_hal_include/furi_hal_nfc.h @@ -10,6 +10,8 @@ #include #include +#include + #ifdef __cplusplus extern "C" { #endif @@ -34,11 +36,17 @@ extern "C" { ((uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_KEEP | \ (uint32_t)RFAL_TXRX_FLAGS_PAR_RX_KEEP | (uint32_t)RFAL_TXRX_FLAGS_PAR_TX_NONE) +#define FURI_HAL_NFC_TX_RAW_RX_DEFAULT \ + ((uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_REMV | \ + (uint32_t)RFAL_TXRX_FLAGS_PAR_RX_REMV | (uint32_t)RFAL_TXRX_FLAGS_PAR_TX_NONE) + typedef enum { FuriHalNfcTxRxTypeDefault, FuriHalNfcTxRxTypeRxNoCrc, FuriHalNfcTxRxTypeRxKeepPar, FuriHalNfcTxRxTypeRaw, + FuriHalNfcTxRxTypeRxRaw, + FuriHalNfcTxRxTransparent, } FuriHalNfcTxRxType; typedef bool (*FuriHalNfcEmulateCallback)( @@ -80,6 +88,7 @@ typedef struct { uint8_t rx_parity[FURI_HAL_NFC_PARITY_BUFF_SIZE]; uint16_t rx_bits; FuriHalNfcTxRxType tx_rx_type; + NfcaSignal* nfca_signal; } FuriHalNfcTxRxContext; /** Init nfc diff --git a/firmware/targets/furi_hal_include/furi_hal_rtc.h b/firmware/targets/furi_hal_include/furi_hal_rtc.h index 21c69b24..bdae3b93 100644 --- a/firmware/targets/furi_hal_include/furi_hal_rtc.h +++ b/firmware/targets/furi_hal_include/furi_hal_rtc.h @@ -93,6 +93,8 @@ void furi_hal_rtc_set_pin_fails(uint32_t value); uint32_t furi_hal_rtc_get_pin_fails(); +uint32_t furi_hal_rtc_datetime_to_timestamp(FuriHalRtcDateTime* datetime); + #ifdef __cplusplus } #endif diff --git a/lib/app-scened-template/file_worker.c b/lib/app-scened-template/file_worker.c deleted file mode 100644 index 3a742ff9..00000000 --- a/lib/app-scened-template/file_worker.c +++ /dev/null @@ -1,443 +0,0 @@ -#include "file_worker.h" -#include -#include -#include -#include - -struct FileWorker { - Storage* 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->api = furi_record_open("storage"); - file_worker->file = storage_file_alloc(file_worker->api); - - return file_worker; -} - -void file_worker_free(FileWorker* file_worker) { - storage_file_free(file_worker->file); - furi_record_close("storage"); - free(file_worker); -} - -bool file_worker_open( - FileWorker* file_worker, - const char* filename, - FS_AccessMode access_mode, - FS_OpenMode open_mode) { - bool result = storage_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) { - if(storage_file_is_open(file_worker->file)) { - storage_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 = storage_common_mkdir(file_worker->api, 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 = storage_common_remove(file_worker->api, 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); -} - -void file_worker_get_next_filename( - FileWorker* file_worker, - const char* dirname, - const char* filename, - const char* fileextension, - string_t nextfilename) { - string_t temp_str; - string_init(temp_str); - uint16_t num = 0; - - string_printf(temp_str, "%s/%s%s", dirname, filename, fileextension); - - while(storage_common_stat(file_worker->api, string_get_cstr(temp_str), NULL) == FSE_OK) { - num++; - string_printf(temp_str, "%s/%s%d%s", dirname, filename, num, fileextension); - } - - if(num) { - string_printf(nextfilename, "%s%d", filename, num); - } else { - string_printf(nextfilename, "%s", filename); - } - - string_clear(temp_str); -} - -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_reset(str_result); - const uint8_t buffer_size = 32; - uint8_t buffer[buffer_size]; - - do { - uint16_t read_count = storage_file_read(file_worker->file, buffer, buffer_size); - if(storage_file_get_error(file_worker->file) != 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) { - UNUSED(file_worker); - DialogsApp* dialogs = furi_record_open("dialogs"); - - DialogMessage* message = dialog_message_alloc(); - dialog_message_set_text(message, error_text, 88, 32, AlignCenter, AlignCenter); - dialog_message_set_icon(message, &I_SDQuestion_35x43, 5, 6); - dialog_message_set_buttons(message, "Back", NULL, NULL); - dialog_message_show(dialogs, message); - dialog_message_free(message); - - furi_record_close("dialogs"); -} - -bool file_worker_file_select( - FileWorker* file_worker, - const char* path, - const char* extension, - char* result, - uint8_t result_size, - const char* selected_filename) { - UNUSED(file_worker); - DialogsApp* dialogs = furi_record_open("dialogs"); - bool ret = - dialog_file_select_show(dialogs, path, extension, result, result_size, selected_filename); - furi_record_close("dialogs"); - return ret; -} - -bool file_worker_check_common_errors(FileWorker* file_worker) { - UNUSED(file_worker); - //TODO remove - /* TODO: [FL-1431] Add return value to file_parser.get_sd_api().check_error() and replace get_fs_info(). */ - 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 = storage_file_read(file_worker->file, buffer, bytes_to_read); - - if(storage_file_get_error(file_worker->file) != 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 = storage_file_write(file_worker->file, buffer, bytes_to_write); - - if(storage_file_get_error(file_worker->file) != 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 = storage_file_tell(file_worker->file); - - if(storage_file_get_error(file_worker->file) != 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) { - storage_file_seek(file_worker->file, position, from_start); - if(storage_file_get_error(file_worker->file) != FSE_OK) { - file_worker_show_error_internal(file_worker, "Cannot seek\nfile"); - return false; - } - - return true; -} - -bool file_worker_read_until_buffered( - FileWorker* file_worker, - string_t str_result, - char* file_buf, - size_t* file_buf_cnt, - size_t file_buf_size, - char separator) { - furi_assert(string_capacity(str_result) > 0); - - // fs_api->file.read now supports up to 512 bytes reading at a time - furi_assert(file_buf_size <= 512); - - string_reset(str_result); - size_t newline_index = 0; - bool found_eol = false; - bool max_length_exceeded = false; - size_t max_length = string_capacity(str_result) - 1; - - while(1) { - if(*file_buf_cnt > 0) { - size_t end_index = 0; - char* endline_ptr = (char*)memchr(file_buf, separator, *file_buf_cnt); - newline_index = endline_ptr - file_buf; - - if(endline_ptr == 0) { - end_index = *file_buf_cnt; - } else if(newline_index < *file_buf_cnt) { - end_index = newline_index + 1; - found_eol = true; - } else { - furi_assert(0); - } - - if(max_length && (string_size(str_result) + end_index > max_length)) - max_length_exceeded = true; - - if(!max_length_exceeded) { - for(size_t i = 0; i < end_index; ++i) { - string_push_back(str_result, file_buf[i]); - } - } - - memmove(file_buf, &file_buf[end_index], *file_buf_cnt - end_index); - *file_buf_cnt = *file_buf_cnt - end_index; - if(found_eol) break; - } - - *file_buf_cnt += storage_file_read( - file_worker->file, &file_buf[*file_buf_cnt], file_buf_size - *file_buf_cnt); - if(storage_file_get_error(file_worker->file) != FSE_OK) { - file_worker_show_error_internal(file_worker, "Cannot read\nfile"); - string_reset(str_result); - *file_buf_cnt = 0; - break; - } - if(*file_buf_cnt == 0) { - break; // end of reading - } - } - - if(max_length_exceeded) string_reset(str_result); - - return string_size(str_result) || *file_buf_cnt; -} - -bool file_worker_get_value_from_key( - FileWorker* file_worker, - string_t key, - char delimiter, - string_t value) { - bool found = false; - string_t next_line; - string_t next_key; - string_init(next_line); - string_init(next_key); - size_t delim_pos = 0; - - while(file_worker_read_until(file_worker, next_line, '\n')) { - delim_pos = string_search_char(next_line, delimiter); - if(delim_pos == STRING_FAILURE) { - break; - } - string_set_n(next_key, next_line, 0, delim_pos); - if(string_equal_p(next_key, key)) { - string_right(next_line, delim_pos); - string_strim(next_line); - string_set(value, next_line); - found = true; - break; - } - } - - string_clear(next_line); - string_clear(next_key); - return found; -} - -bool file_worker_rename(FileWorker* file_worker, const char* old_path, const char* new_path) { - FS_Error fs_result = storage_common_rename(file_worker->api, old_path, new_path); - - if(fs_result != FSE_OK && fs_result != FSE_EXIST) { - file_worker_show_error_internal(file_worker, "Cannot rename\n file/directory"); - return false; - } - - return file_worker_check_common_errors(file_worker); -} - -bool file_worker_check_errors(FileWorker* file_worker) { - return file_worker_check_common_errors(file_worker); -} - -bool file_worker_is_file_exist(FileWorker* file_worker, const char* filename, bool* exist) { - File* file = storage_file_alloc(file_worker->api); - - *exist = storage_file_open(file, filename, FSAM_READ, FSOM_OPEN_EXISTING); - storage_file_close(file); - storage_file_free(file); - - bool result = file_worker_check_common_errors(file_worker); - return result; -} diff --git a/lib/app-scened-template/file_worker.h b/lib/app-scened-template/file_worker.h deleted file mode 100644 index 1b5f3407..00000000 --- a/lib/app-scened-template/file_worker.h +++ /dev/null @@ -1,249 +0,0 @@ -#pragma once -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief File operations helper class. - * Automatically opens API records, shows error message on error. - */ -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 Get next free filename. - * - * @param file_worker FileWorker instance - * @param dirname - * @param filename - * @param fileextension - * @param nextfilename return name - */ -void file_worker_get_next_filename( - FileWorker* file_worker, - const char* dirname, - const char* filename, - const char* fileextension, - string_t nextfilename); - -/** - * @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 path to directory - * @param extension file extension to be offered for selection - * @param selected_filename buffer where the selected filename will be saved - * @param selected_filename_size and the size of this buffer - * @param preselected_filename filename to be preselected - * @return bool whether a file was selected - */ -bool file_worker_file_select( - FileWorker* file_worker, - const char* path, - const char* extension, - char* selected_filename, - uint8_t selected_filename_size, - const char* preselected_filename); - -/** - * @brief Reads data from a file until separator or EOF is found. - * The separator is included in the result. - * - * @param file_worker FileWorker instance - * @param str_result - * @param file_buf - * @param file_buf_cnt - * @param max_length - * @param separator - * @return true on success - */ -bool file_worker_read_until_buffered( - FileWorker* file_worker, - string_t str_result, - char* file_buf, - size_t* file_buf_cnt, - size_t max_length, - char separator); - -/** - * @brief Gets value from key - * - * @param file_worker FileWorker instance - * @param key key - * @param delimeter key-value delimeter - * @param value value for given key - * @return true on success - */ -bool file_worker_get_value_from_key( - FileWorker* file_worker, - string_t key, - char delimiter, - string_t value); - -/** - * @brief Check whether file exist or not - * - * @param file_worker FileWorker instance - * @param filename - * @param exist - flag to show file exist - * @return true on success - */ -bool file_worker_is_file_exist(FileWorker* file_worker, const char* filename, bool* exist); - -/** - * @brief Rename file or directory - * - * @param file_worker FileWorker instance - * @param old_filename - * @param new_filename - * @return true on success - */ -bool file_worker_rename(FileWorker* file_worker, const char* old_path, const char* new_path); - -/** - * @brief Check errors - * - * @param file_worker FileWorker instance - * @return true on success - */ -bool file_worker_check_errors(FileWorker* file_worker); - -#ifdef __cplusplus -} -#endif diff --git a/lib/app-scened-template/file_worker_cpp.cpp b/lib/app-scened-template/file_worker_cpp.cpp deleted file mode 100644 index 59a08a01..00000000 --- a/lib/app-scened-template/file_worker_cpp.cpp +++ /dev/null @@ -1,94 +0,0 @@ -#include "file_worker_cpp.h" -#include - -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, - const char* selected_filename) { - return file_worker_file_select( - file_worker, path, extension, result, result_size, selected_filename); -} - -bool FileWorkerCpp::read_until_buffered( - string_t str_result, - char* file_buf, - size_t* file_buf_cnt, - size_t max_length, - char separator) { - return file_worker_read_until_buffered( - file_worker, str_result, file_buf, file_buf_cnt, max_length, separator); -} - -bool FileWorkerCpp::get_value_from_key(string_t key, char delimiter, string_t value) { - return file_worker_get_value_from_key(file_worker, key, delimiter, value); -} - -bool FileWorkerCpp::is_file_exist(const char* filename, bool* exist) { - return file_worker_is_file_exist(file_worker, filename, exist); -} - -bool FileWorkerCpp::rename(const char* old_path, const char* new_path) { - return file_worker_rename(file_worker, old_path, new_path); -} - -bool FileWorkerCpp::check_errors() { - return file_worker_check_errors(file_worker); -} diff --git a/lib/app-scened-template/file_worker_cpp.h b/lib/app-scened-template/file_worker_cpp.h deleted file mode 100644 index eba622af..00000000 --- a/lib/app-scened-template/file_worker_cpp.h +++ /dev/null @@ -1,190 +0,0 @@ -#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, - const char* selected_filename); - - /** - * @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 included in the result. - * - * @param result - * @param file_buf - * @param file_buf_cnt - * @param max_length - * @param separator - * @return true on success - */ - bool read_until_buffered( - string_t str_result, - char* file_buf, - size_t* file_buf_cnt, - size_t max_length, - char separator = '\n'); - - /** - * @brief Gets value from key - * - * @param file_worker FileWorker instance - * @param key key - * @param delimeter key-value delimeter - * @param value value for given key - * @return true on success - */ - bool get_value_from_key(string_t key, char delimiter, string_t value); - - /** - * @brief Check whether file exist or not - * - * @param file_worker FileWorker instance - * @param filename - * @param exist - flag to show file exist - * @return true on success - */ - bool is_file_exist(const char* filename, bool* exist); - - /** - * @brief Rename file or directory - * - * @param old_filename - * @param new_filename - * @return true on success - */ - bool rename(const char* old_path, const char* new_path); - - /** - * @brief Check errors - * - * @return true if no errors - */ - bool check_errors(); - -private: - FileWorker* file_worker; -}; diff --git a/lib/app-template/app_template.cpp b/lib/app-template/app_template.cpp deleted file mode 100644 index ba73945c..00000000 --- a/lib/app-template/app_template.cpp +++ /dev/null @@ -1,102 +0,0 @@ -#include "app_template.h" - -/* -To use this example you need to rename -AppExampleState - class to hold app state -AppExampleEvent - class to hold app event -AppExample - app class -app_cpp_example - function that launch app -*/ - -// event enumeration type -typedef uint8_t event_t; - -// app state class -class AppExampleState { -public: - // state data - uint8_t example_data; - - // state initializer - AppExampleState() { - example_data = 0; - } -}; - -// app events class -class AppExampleEvent { -public: - // events enum - static const event_t EventTypeTick = 0; - static const event_t EventTypeKey = 1; - - // payload - union { - InputEvent input; - } value; - - // event type - event_t type; -}; - -// our app derived from base AppTemplate class -// with template variables -class AppExample : public AppTemplate { -public: - uint8_t run(); - void render(Canvas* canvas); -}; - -// start app -uint8_t AppExample::run() { - // here we dont need to acquire or release state - // because before we call app_ready our application is "single threaded" - state.example_data = 12; - - // signal that we ready to render and ipc - app_ready(); - - // from here, any data that pass in render function must be guarded - // by calling acquire_state and release_state - - AppExampleEvent event; - while(1) { - if(get_event(&event, 1000)) { - if(event.type == AppExampleEvent::EventTypeKey) { - // press events - if(event.value.input.type == InputTypeShort && - event.value.input.key == InputKeyBack) { - printf("bye!\n"); - return exit(); - } - - if(event.value.input.type == InputTypeShort && - event.value.input.key == InputKeyUp) { - // to read or write state you need to execute - // acquire modify release state - acquire_state(); - state.example_data = 24; - release_state(); - } - } - } - - // signal to force gui update - update_gui(); - }; -} - -// render app -void AppExample::render(Canvas* canvas) { - // here you dont need to call acquire_state or release_state - // to read or write app state, that already handled by caller - canvas_set_color(canvas, ColorBlack); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str(canvas, 2, state.example_data, "Example app"); -} - -// app enter function -extern "C" uint8_t app_cpp_example(void* p) { - AppExample* app = new AppExample(); - return app->run(); -} diff --git a/lib/app-template/app_template.h b/lib/app-template/app_template.h deleted file mode 100644 index 7adcf2bf..00000000 --- a/lib/app-template/app_template.h +++ /dev/null @@ -1,113 +0,0 @@ -#pragma once - -#include "callback-connector.h" -#include -#include -#include -#include - -// simple app class with template variables -template class AppTemplate { -public: - ViewPort* view_port; - osMessageQueueId_t event_queue; - TState state; - ValueMutex state_mutex; - Gui* gui; - - AppTemplate(); - ~AppTemplate(); - void input_callback(InputEvent* input_event, void* ctx); - void draw_callback(Canvas* canvas, void* ctx); - virtual void render(Canvas* canvas) = 0; - void acquire_state(void); - void release_state(void); - bool get_event(TEvent* event, uint32_t timeout); - void app_ready(void); - uint8_t exit(void); - void update_gui(void); -}; - -template AppTemplate::AppTemplate() { - // allocate events queue - event_queue = osMessageQueueNew(10, sizeof(TEvent), NULL); - - // allocate valuemutex - // TODO: use plain os mutex? - if(!init_mutex(&state_mutex, &state, sizeof(TState))) { - printf("cannot create mutex\n"); - furi_crash(NULL); - } - - // open gui - gui = (Gui*)furi_record_open("gui"); - - // allocate view_port - view_port = view_port_alloc(); -} - -template AppTemplate::~AppTemplate() { -} - -// generic input callback -template -void AppTemplate::input_callback(InputEvent* input_event, void* ctx) { - AppTemplate* app = static_cast(ctx); - - TEvent event; - event.type = TEvent::EventTypeKey; - event.value.input = *input_event; - osMessageQueuePut(app->event_queue, &event, 0, 0); -} - -// generic draw callback -template -void AppTemplate::draw_callback(Canvas* canvas, void* ctx) { - AppTemplate* app = static_cast(ctx); - app->acquire_state(); - - canvas_clear(canvas); - app->render(canvas); - - app->release_state(); -} - -template void AppTemplate::acquire_state(void) { - acquire_mutex(&state_mutex, osWaitForever); -} - -template void AppTemplate::release_state(void) { - release_mutex(&state_mutex, &state); -} - -template -bool AppTemplate::get_event(TEvent* event, uint32_t timeout) { - osStatus_t event_status = osMessageQueueGet(event_queue, event, NULL, timeout); - - return (event_status == osOK); -} - -// signal that app is ready, and we can render something -// also unblock dependent tasks -template void AppTemplate::app_ready(void) { - // connect view_port with input callback - auto input_cb_ref = cbc::obtain_connector(this, &AppTemplate::input_callback); - view_port_input_callback_set(view_port, input_cb_ref, this); - - // connect view_port with draw callback - auto draw_cb_ref = cbc::obtain_connector(this, &AppTemplate::draw_callback); - view_port_draw_callback_set(view_port, draw_cb_ref, this); - - // add view_port - gui_add_view_port(gui, view_port, GuiLayerFullscreen); -} - -template uint8_t AppTemplate::exit(void) { - // TODO remove all view_ports create by app - view_port_enabled_set(view_port, false); - return 255; -} - -template void AppTemplate::update_gui(void) { - view_port_update(view_port); -} diff --git a/lib/digital_signal/digital_signal.c b/lib/digital_signal/digital_signal.c new file mode 100644 index 00000000..23ddaf90 --- /dev/null +++ b/lib/digital_signal/digital_signal.c @@ -0,0 +1,173 @@ +#include "digital_signal.h" + +#include +#include +#include +#include + +#define F_TIM (64000000.0) +#define T_TIM (1.0 / F_TIM) + +DigitalSignal* digital_signal_alloc(uint32_t max_edges_cnt) { + DigitalSignal* signal = malloc(sizeof(DigitalSignal)); + signal->start_level = true; + signal->edges_max_cnt = max_edges_cnt; + signal->edge_timings = malloc(max_edges_cnt * sizeof(float)); + signal->reload_reg_buff = malloc(max_edges_cnt * sizeof(uint32_t)); + signal->edge_cnt = 0; + + return signal; +} + +void digital_signal_free(DigitalSignal* signal) { + furi_assert(signal); + + free(signal->edge_timings); + free(signal->reload_reg_buff); + free(signal); +} + +bool digital_signal_append(DigitalSignal* signal_a, DigitalSignal* signal_b) { + furi_assert(signal_a); + furi_assert(signal_b); + + if(signal_a->edges_max_cnt < signal_a->edge_cnt + signal_b->edge_cnt) { + return false; + } + + bool end_level = signal_a->start_level; + if(signal_a->edge_cnt) { + end_level = signal_a->start_level ^ !(signal_a->edge_cnt % 2); + } + uint8_t start_copy = 0; + if(end_level == signal_b->start_level) { + if(signal_a->edge_cnt) { + signal_a->edge_timings[signal_a->edge_cnt - 1] += signal_b->edge_timings[0]; + start_copy += 1; + } else { + signal_a->edge_timings[signal_a->edge_cnt] += signal_b->edge_timings[0]; + } + } + memcpy( + &signal_a->edge_timings[signal_a->edge_cnt], + &signal_b->edge_timings[start_copy], + (signal_b->edge_cnt - start_copy) * sizeof(float)); + signal_a->edge_cnt += signal_b->edge_cnt - start_copy; + + return true; +} + +bool digital_signal_get_start_level(DigitalSignal* signal) { + furi_assert(signal); + + return signal->start_level; +} + +uint32_t digital_signal_get_edges_cnt(DigitalSignal* signal) { + furi_assert(signal); + + return signal->edge_cnt; +} + +float digital_signal_get_edge(DigitalSignal* signal, uint32_t edge_num) { + furi_assert(signal); + furi_assert(edge_num < signal->edge_cnt); + + return signal->edge_timings[edge_num]; +} + +static void digital_signal_prepare_arr(DigitalSignal* signal) { + float t_signal = 0; + float t_current = 0; + float r = 0; + float r_int = 0; + float r_dec = 0; + + for(size_t i = 0; i < signal->edge_cnt - 1; i++) { + t_signal += signal->edge_timings[i]; + r = (t_signal - t_current) / T_TIM; + r_dec = modff(r, &r_int); + if(r_dec < 0.5f) { + signal->reload_reg_buff[i] = (uint32_t)r_int - 1; + } else { + signal->reload_reg_buff[i] = (uint32_t)r_int; + } + t_current += (signal->reload_reg_buff[i] + 1) * T_TIM; + } +} + +bool digital_signal_send(DigitalSignal* signal, const GpioPin* gpio) { + furi_assert(signal); + furi_assert(gpio); + + // Configure gpio as output + furi_hal_gpio_init(gpio, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + + // Init gpio buffer and DMA channel + uint16_t gpio_reg = gpio->port->ODR; + uint16_t gpio_buff[2]; + if(signal->start_level) { + gpio_buff[0] = gpio_reg | gpio->pin; + gpio_buff[1] = gpio_reg & ~(gpio->pin); + } else { + gpio_buff[0] = gpio_reg & ~(gpio->pin); + gpio_buff[1] = gpio_reg | gpio->pin; + } + LL_DMA_InitTypeDef dma_config = {}; + dma_config.MemoryOrM2MDstAddress = (uint32_t)gpio_buff; + dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (gpio->port->ODR); + dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; + dma_config.Mode = LL_DMA_MODE_CIRCULAR; + dma_config.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; + dma_config.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; + dma_config.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_HALFWORD; + dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_HALFWORD; + dma_config.NbData = 2; + dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; + dma_config.Priority = LL_DMA_PRIORITY_VERYHIGH; + LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &dma_config); + LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, 2); + LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1); + + // Init timer arr register buffer and DMA channel + digital_signal_prepare_arr(signal); + dma_config.MemoryOrM2MDstAddress = (uint32_t)signal->reload_reg_buff; + dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (TIM2->ARR); + dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; + dma_config.Mode = LL_DMA_MODE_NORMAL; + dma_config.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; + dma_config.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; + dma_config.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; + dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; + dma_config.NbData = signal->edge_cnt - 2; + dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; + dma_config.Priority = LL_DMA_PRIORITY_HIGH; + LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &dma_config); + LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_2, signal->edge_cnt - 2); + LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2); + + // Set up timer + LL_TIM_SetCounterMode(TIM2, LL_TIM_COUNTERMODE_UP); + LL_TIM_SetClockDivision(TIM2, LL_TIM_CLOCKDIVISION_DIV1); + LL_TIM_SetPrescaler(TIM2, 0); + LL_TIM_SetAutoReload(TIM2, 10); + LL_TIM_SetCounter(TIM2, 0); + LL_TIM_EnableUpdateEvent(TIM2); + LL_TIM_EnableDMAReq_UPDATE(TIM2); + + // Start transactions + LL_TIM_GenerateEvent_UPDATE(TIM2); // Do we really need it? + LL_TIM_EnableCounter(TIM2); + + while(!LL_DMA_IsActiveFlag_TC2(DMA1)) + ; + + LL_DMA_ClearFlag_TC1(DMA1); + LL_DMA_ClearFlag_TC2(DMA1); + LL_TIM_DisableCounter(TIM2); + LL_TIM_SetCounter(TIM2, 0); + LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2); + + return true; +} diff --git a/lib/digital_signal/digital_signal.h b/lib/digital_signal/digital_signal.h new file mode 100644 index 00000000..5e20e733 --- /dev/null +++ b/lib/digital_signal/digital_signal.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include + +#include + +typedef struct { + bool start_level; + uint32_t edge_cnt; + uint32_t edges_max_cnt; + float* edge_timings; + uint32_t* reload_reg_buff; +} DigitalSignal; + +DigitalSignal* digital_signal_alloc(uint32_t max_edges_cnt); + +void digital_signal_free(DigitalSignal* signal); + +bool digital_signal_append(DigitalSignal* signal_a, DigitalSignal* signal_b); + +bool digital_signal_get_start_level(DigitalSignal* signal); + +uint32_t digital_signal_get_edges_cnt(DigitalSignal* signal); + +float digital_signal_get_edge(DigitalSignal* signal, uint32_t edge_num); + +bool digital_signal_send(DigitalSignal* signal, const GpioPin* gpio); diff --git a/lib/flipper_format/flipper_format.c b/lib/flipper_format/flipper_format.c index cee76701..df848ead 100644 --- a/lib/flipper_format/flipper_format.c +++ b/lib/flipper_format/flipper_format.c @@ -185,6 +185,37 @@ bool flipper_format_write_string_cstr( return result; } +bool flipper_format_read_hex_uint64( + FlipperFormat* flipper_format, + const char* key, + uint64_t* data, + const uint16_t data_size) { + furi_assert(flipper_format); + return flipper_format_stream_read_value_line( + flipper_format->stream, + key, + FlipperStreamValueHexUint64, + data, + data_size, + flipper_format->strict_mode); +} + +bool flipper_format_write_hex_uint64( + FlipperFormat* flipper_format, + const char* key, + const uint64_t* data, + const uint16_t data_size) { + furi_assert(flipper_format); + FlipperStreamWriteData write_data = { + .key = key, + .type = FlipperStreamValueHexUint64, + .data = data, + .data_size = data_size, + }; + bool result = flipper_format_stream_write_value_line(flipper_format->stream, &write_data); + return result; +} + bool flipper_format_read_uint32( FlipperFormat* flipper_format, const char* key, diff --git a/lib/flipper_format/flipper_format.h b/lib/flipper_format/flipper_format.h index 49e3f936..e32a5219 100644 --- a/lib/flipper_format/flipper_format.h +++ b/lib/flipper_format/flipper_format.h @@ -273,6 +273,34 @@ bool flipper_format_write_string_cstr( const char* key, const char* data); +/** + * Read array of uint64 in hex format by key + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @param data_size Values count + * @return True on success + */ +bool flipper_format_read_hex_uint64( + FlipperFormat* flipper_format, + const char* key, + uint64_t* data, + const uint16_t data_size); + +/** + * Write key and array of uint64 in hex format + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @param data_size Values count + * @return True on success + */ +bool flipper_format_write_hex_uint64( + FlipperFormat* flipper_format, + const char* key, + const uint64_t* data, + const uint16_t data_size); + /** * Read array of uint32 by key * @param flipper_format Pointer to a FlipperFormat instance diff --git a/lib/flipper_format/flipper_format_stream.c b/lib/flipper_format/flipper_format_stream.c index bd700849..5c210536 100644 --- a/lib/flipper_format/flipper_format_stream.c +++ b/lib/flipper_format/flipper_format_stream.c @@ -287,6 +287,11 @@ bool flipper_format_stream_write_value_line(Stream* stream, FlipperStreamWriteDa const uint32_t* data = write_data->data; string_printf(value, "%" PRId32, data[i]); }; break; + case FlipperStreamValueHexUint64: { + const uint64_t* data = write_data->data; + string_printf( + value, "%08lX%08lX", (uint32_t)(data[i] >> 32), (uint32_t)data[i]); + }; break; case FlipperStreamValueBool: { const bool* data = write_data->data; string_printf(value, data[i] ? "true" : "false"); @@ -380,6 +385,14 @@ bool flipper_format_stream_read_value_line( uint32_t* data = _data; scan_values = sscanf(string_get_cstr(value), "%" PRId32, &data[i]); }; break; + case FlipperStreamValueHexUint64: { + uint64_t* data = _data; + if(string_size(value) >= 16) { + if(hex_chars_to_uint64(string_get_cstr(value), &data[i])) { + scan_values = 1; + } + } + }; break; case FlipperStreamValueBool: { bool* data = _data; data[i] = !string_cmpi_str(value, "true"); diff --git a/lib/flipper_format/flipper_format_stream.h b/lib/flipper_format/flipper_format_stream.h index 6ac17322..75eaef20 100644 --- a/lib/flipper_format/flipper_format_stream.h +++ b/lib/flipper_format/flipper_format_stream.h @@ -15,6 +15,7 @@ typedef enum { FlipperStreamValueFloat, FlipperStreamValueInt32, FlipperStreamValueUint32, + FlipperStreamValueHexUint64, FlipperStreamValueBool, } FlipperStreamValue; diff --git a/lib/lib.mk b/lib/lib.mk index cb58fade..43f4a1a2 100644 --- a/lib/lib.mk +++ b/lib/lib.mk @@ -95,6 +95,11 @@ C_SOURCES += $(wildcard $(LIB_DIR)/toolbox/*/*.c) CPP_SOURCES += $(wildcard $(LIB_DIR)/toolbox/*.cpp) CPP_SOURCES += $(wildcard $(LIB_DIR)/toolbox/*/*.cpp) +# Digital signal +CFLAGS += -I$(LIB_DIR)/digital_signal +C_SOURCES += $(wildcard $(LIB_DIR)/digital_signal/*.c) + + # USB Stack CFLAGS += -I$(LIB_DIR)/libusb_stm32/inc C_SOURCES += $(LIB_DIR)/libusb_stm32/src/usbd_stm32wb55_devfs.c diff --git a/lib/nfc_protocols/crypto1.c b/lib/nfc_protocols/crypto1.c index 469b0de0..f08164ba 100644 --- a/lib/nfc_protocols/crypto1.c +++ b/lib/nfc_protocols/crypto1.c @@ -58,7 +58,7 @@ uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted) { return out; } -uint8_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted) { +uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted) { furi_assert(crypto1); uint32_t out = 0; for(uint8_t i = 0; i < 32; i++) { diff --git a/lib/nfc_protocols/crypto1.h b/lib/nfc_protocols/crypto1.h index aaa2470c..07b39c22 100644 --- a/lib/nfc_protocols/crypto1.h +++ b/lib/nfc_protocols/crypto1.h @@ -16,7 +16,7 @@ uint8_t crypto1_bit(Crypto1* crypto1, uint8_t in, int is_encrypted); uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted); -uint8_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted); +uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted); uint32_t crypto1_filter(uint32_t in); diff --git a/lib/nfc_protocols/emv.c b/lib/nfc_protocols/emv.c index 8e14cf48..416e63a5 100644 --- a/lib/nfc_protocols/emv.c +++ b/lib/nfc_protocols/emv.c @@ -10,6 +10,9 @@ const PDOLValue pdol_merchant_type = {0x9F58, {0x01}}; // Merchant type indicato const PDOLValue pdol_term_trans_qualifies = { 0x9F66, {0x79, 0x00, 0x40, 0x80}}; // Terminal transaction qualifiers +const PDOLValue pdol_addtnl_term_qualifies = { + 0x9F40, + {0x79, 0x00, 0x40, 0x80}}; // Terminal transaction qualifiers const PDOLValue pdol_amount_authorise = { 0x9F02, {0x00, 0x00, 0x00, 0x10, 0x00, 0x00}}; // Amount, authorised @@ -30,6 +33,7 @@ const PDOLValue* const pdol_values[] = { &pdol_term_type, &pdol_merchant_type, &pdol_term_trans_qualifies, + &pdol_addtnl_term_qualifies, &pdol_amount_authorise, &pdol_amount, &pdol_country_code, @@ -61,6 +65,11 @@ static const uint8_t pdol_ans[] = {0x77, 0x40, 0x82, 0x02, 0x20, 0x00, 0x57, 0x1 static void emv_trace(FuriHalNfcTxRxContext* tx_rx, const char* message) { if(furi_log_get_level() == FuriLogLevelTrace) { FURI_LOG_T(TAG, "%s", message); + printf("TX: "); + for(size_t i = 0; i < tx_rx->tx_bits / 8; i++) { + printf("%02X ", tx_rx->tx_data[i]); + } + printf("\r\nRX: "); for(size_t i = 0; i < tx_rx->rx_bits / 8; i++) { printf("%02X ", tx_rx->rx_data[i]); } @@ -68,42 +77,109 @@ static void emv_trace(FuriHalNfcTxRxContext* tx_rx, const char* message) { } } -static uint16_t emv_parse_TLV(uint8_t* dest, uint8_t* src, uint16_t* idx) { - uint8_t len = src[*idx + 1]; - memcpy(dest, &src[*idx + 2], len); - *idx = *idx + len + 1; - return len; -} - -static bool emv_decode_search_tag_u16_r(uint16_t tag, uint8_t* buff, uint16_t* idx) { - if((buff[*idx] << 8 | buff[*idx + 1]) == tag) { - *idx = *idx + 3; - return true; - } - return false; -} - -bool emv_decode_ppse_response(uint8_t* buff, uint16_t len, EmvApplication* app) { +static bool emv_decode_response(uint8_t* buff, uint16_t len, EmvApplication* app) { uint16_t i = 0; - bool app_aid_found = false; + uint16_t tag = 0, first_byte = 0; + uint16_t tlen = 0; + bool success = false; while(i < len) { - if(buff[i] == EMV_TAG_APP_TEMPLATE) { - uint8_t app_len = buff[++i]; - for(uint16_t j = i; j < MIN(i + app_len, len - 1); j++) { - if(buff[j] == EMV_TAG_AID) { - app_aid_found = true; - app->aid_len = buff[j + 1]; - emv_parse_TLV(app->aid, buff, &j); - } else if(buff[j] == EMV_TAG_PRIORITY) { - emv_parse_TLV(&app->priority, buff, &j); - } - } - i += app_len; + first_byte = buff[i]; + if((first_byte & 31) == 31) { // 2-byte tag + tag = buff[i] << 8 | buff[i + 1]; + i++; + FURI_LOG_T(TAG, " 2-byte TLV EMV tag: %x", tag); + } else { + tag = buff[i]; + FURI_LOG_T(TAG, " 1-byte TLV EMV tag: %x", tag); } i++; + tlen = buff[i]; + if((tlen & 128) == 128) { // long length value + i++; + tlen = buff[i]; + FURI_LOG_T(TAG, " 2-byte TLV length: %d", tlen); + } else { + FURI_LOG_T(TAG, " 1-byte TLV length: %d", tlen); + } + i++; + if((first_byte & 32) == 32) { // "Constructed" -- contains more TLV data to parse + FURI_LOG_T(TAG, "Constructed TLV %x", tag); + if(!emv_decode_response(&buff[i], tlen, app)) { + FURI_LOG_T(TAG, "Failed to decode response for %x", tag); + // return false; + } else { + success = true; + } + } else { + switch(tag) { + case EMV_TAG_AID: + app->aid_len = tlen; + memcpy(app->aid, &buff[i], tlen); + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_AID %x", tag); + break; + case EMV_TAG_PRIORITY: + memcpy(&app->priority, &buff[i], tlen); + success = true; + break; + case EMV_TAG_CARD_NAME: + memcpy(app->name, &buff[i], tlen); + app->name[tlen] = '\0'; + app->name_found = true; + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_CARD_NAME %x : %s", tag, app->name); + break; + case EMV_TAG_PDOL: + memcpy(app->pdol.data, &buff[i], tlen); + app->pdol.size = tlen; + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_PDOL %x (len=%d)", tag, tlen); + break; + case EMV_TAG_AFL: + memcpy(app->afl.data, &buff[i], tlen); + app->afl.size = tlen; + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_AFL %x (len=%d)", tag, tlen); + break; + case EMV_TAG_CARD_NUM: // Track 2 Equivalent Data. 0xD0 delimits PAN from expiry (YYMM) + for(int x = 1; x < tlen; x++) { + if(buff[i + x + 1] > 0xD0) { + memcpy(app->card_number, &buff[i], x + 1); + app->card_number_len = x + 1; + break; + } + } + success = true; + FURI_LOG_T( + TAG, + "found EMV_TAG_CARD_NUM %x (len=%d)", + EMV_TAG_CARD_NUM, + app->card_number_len); + break; + case EMV_TAG_PAN: + memcpy(app->card_number, &buff[i], tlen); + app->card_number_len = tlen; + success = true; + break; + case EMV_TAG_EXP_DATE: + app->exp_year = buff[i]; + app->exp_month = buff[i + 1]; + success = true; + break; + case EMV_TAG_CURRENCY_CODE: + app->currency_code = (buff[i] << 8 | buff[i + 1]); + success = true; + break; + case EMV_TAG_COUNTRY_CODE: + app->country_code = (buff[i] << 8 | buff[i + 1]); + success = true; + break; + } + } + i += tlen; } - return app_aid_found; + return success; } bool emv_select_ppse(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { @@ -124,7 +200,7 @@ bool emv_select_ppse(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { FURI_LOG_D(TAG, "Send select PPSE"); if(furi_hal_nfc_tx_rx(tx_rx, 300)) { emv_trace(tx_rx, "Select PPSE answer:"); - if(emv_decode_ppse_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { + if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { app_aid_found = true; } else { FURI_LOG_E(TAG, "Failed to parse application"); @@ -136,28 +212,6 @@ bool emv_select_ppse(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { return app_aid_found; } -static bool emv_decode_select_app_response(uint8_t* buff, uint16_t len, EmvApplication* app) { - uint16_t i = 0; - bool decode_success = false; - - while(i < len) { - if(buff[i] == EMV_TAG_CARD_NAME) { - uint8_t name_len = buff[i + 1]; - emv_parse_TLV((uint8_t*)app->name, buff, &i); - app->name[name_len] = '\0'; - app->name_found = true; - decode_success = true; - } else if(((buff[i] << 8) | buff[i + 1]) == EMV_TAG_PDOL) { - i++; - app->pdol.size = emv_parse_TLV(app->pdol.data, buff, &i); - decode_success = true; - } - i++; - } - - return decode_success; -} - bool emv_select_app(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { bool select_app_success = false; const uint8_t emv_select_header[] = { @@ -181,7 +235,7 @@ bool emv_select_app(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { FURI_LOG_D(TAG, "Start application"); if(furi_hal_nfc_tx_rx(tx_rx, 300)) { emv_trace(tx_rx, "Start application answer:"); - if(emv_decode_select_app_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { + if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { select_app_success = true; } else { FURI_LOG_E(TAG, "Failed to read PAN or PDOL"); @@ -226,22 +280,6 @@ static uint16_t emv_prepare_pdol(APDU* dest, APDU* src) { return dest->size; } -static bool emv_decode_get_proc_opt(uint8_t* buff, uint16_t len, EmvApplication* app) { - bool card_num_read = false; - - for(uint16_t i = 0; i < len; i++) { - if(buff[i] == EMV_TAG_CARD_NUM) { - app->card_number_len = 8; - memcpy(app->card_number, &buff[i + 2], app->card_number_len); - card_num_read = true; - } else if(buff[i] == EMV_TAG_AFL) { - app->afl.size = emv_parse_TLV(app->afl.data, buff, &i); - } - } - - return card_num_read; -} - static bool emv_get_processing_options(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { bool card_num_read = false; const uint8_t emv_gpo_header[] = {0x80, 0xA8, 0x00, 0x00}; @@ -264,8 +302,10 @@ static bool emv_get_processing_options(FuriHalNfcTxRxContext* tx_rx, EmvApplicat FURI_LOG_D(TAG, "Get proccessing options"); if(furi_hal_nfc_tx_rx(tx_rx, 300)) { emv_trace(tx_rx, "Get processing options answer:"); - if(emv_decode_get_proc_opt(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { - card_num_read = true; + if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { + if(app->card_number_len > 0) { + card_num_read = true; + } } } else { FURI_LOG_E(TAG, "Failed to get processing options"); @@ -274,31 +314,6 @@ static bool emv_get_processing_options(FuriHalNfcTxRxContext* tx_rx, EmvApplicat return card_num_read; } -static bool emv_decode_read_sfi_record(uint8_t* buff, uint16_t len, EmvApplication* app) { - bool pan_parsed = false; - - for(uint16_t i = 0; i < len; i++) { - if(buff[i] == EMV_TAG_PAN) { - if(buff[i + 1] == 8 || buff[i + 1] == 10) { - app->card_number_len = buff[i + 1]; - memcpy(app->card_number, &buff[i + 2], app->card_number_len); - pan_parsed = true; - } - } else if(emv_decode_search_tag_u16_r(EMV_TAG_EXP_DATE, buff, &i)) { - app->exp_year = buff[i++]; - app->exp_month = buff[i++]; - } else if(emv_decode_search_tag_u16_r(EMV_TAG_CURRENCY_CODE, buff, &i)) { - app->currency_code = (buff[i] << 8) | buff[i + 1]; - i += 2; - } else if(emv_decode_search_tag_u16_r(EMV_TAG_COUNTRY_CODE, buff, &i)) { - app->country_code = (buff[i] << 8) | buff[i + 1]; - i += 1; - } - } - - return pan_parsed; -} - static bool emv_read_sfi_record( FuriHalNfcTxRxContext* tx_rx, EmvApplication* app, @@ -320,7 +335,7 @@ static bool emv_read_sfi_record( if(furi_hal_nfc_tx_rx(tx_rx, 300)) { emv_trace(tx_rx, "SFI record:"); - if(emv_decode_read_sfi_record(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { + if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { card_num_read = true; } } else { diff --git a/lib/nfc_protocols/mifare_classic.c b/lib/nfc_protocols/mifare_classic.c index ace37bff..38c47127 100644 --- a/lib/nfc_protocols/mifare_classic.c +++ b/lib/nfc_protocols/mifare_classic.c @@ -1,6 +1,7 @@ #include "mifare_classic.h" #include "nfca.h" #include "nfc_util.h" +#include // Algorithm from https://github.com/RfidResearchGroup/proxmark3.git @@ -10,6 +11,20 @@ #define MF_CLASSIC_AUTH_KEY_B_CMD (0x61U) #define MF_CLASSIC_READ_SECT_CMD (0x30) +typedef enum { + MfClassicActionDataRead, + MfClassicActionDataWrite, + MfClassicActionDataInc, + MfClassicActionDataDec, + + MfClassicActionKeyARead, + MfClassicActionKeyAWrite, + MfClassicActionKeyBRead, + MfClassicActionKeyBWrite, + MfClassicActionACRead, + MfClassicActionACWrite, +} MfClassicAction; + static uint8_t mf_classic_get_first_block_num_of_sector(uint8_t sector) { furi_assert(sector < 40); if(sector < 32) { @@ -19,11 +34,31 @@ static uint8_t mf_classic_get_first_block_num_of_sector(uint8_t sector) { } } +static uint8_t mf_classic_get_sector_by_block(uint8_t block) { + if(block < 128) { + return (block | 0x03) / 4; + } else { + return 32 + ((block | 0xf) - 32 * 4) / 16; + } +} + static uint8_t mf_classic_get_blocks_num_in_sector(uint8_t sector) { furi_assert(sector < 40); return sector < 32 ? 4 : 16; } +static uint8_t mf_classic_get_sector_trailer(uint8_t block) { + if(block < 128) { + return block | 0x03; + } else { + return block | 0x0f; + } +} + +static bool mf_classic_is_sector_trailer(uint8_t block) { + return block == mf_classic_get_sector_trailer(block); +} + uint8_t mf_classic_get_total_sectors_num(MfClassicReader* reader) { furi_assert(reader); if(reader->type == MfClassicType1k) { @@ -35,6 +70,132 @@ uint8_t mf_classic_get_total_sectors_num(MfClassicReader* reader) { } } +static uint16_t mf_classic_get_total_block_num(MfClassicType type) { + if(type == MfClassicType1k) { + return 64; + } else if(type == MfClassicType4k) { + return 256; + } else { + return 0; + } +} + +static bool mf_classic_is_allowed_access_sector_trailer( + MfClassicEmulator* emulator, + uint8_t block_num, + MfClassicKey key, + MfClassicAction action) { + uint8_t* sector_trailer = emulator->data.block[block_num].value; + uint8_t AC = ((sector_trailer[7] >> 5) & 0x04) | ((sector_trailer[8] >> 2) & 0x02) | + ((sector_trailer[8] >> 7) & 0x01); + switch(action) { + case MfClassicActionKeyARead: { + return false; + } + case MfClassicActionKeyAWrite: { + return ( + (key == MfClassicKeyA && (AC == 0x00 || AC == 0x01)) || + (key == MfClassicKeyB && (AC == 0x04 || AC == 0x03))); + } + case MfClassicActionKeyBRead: { + return (key == MfClassicKeyA && (AC == 0x00 || AC == 0x02 || AC == 0x01)); + } + case MfClassicActionKeyBWrite: { + return ( + (key == MfClassicKeyA && (AC == 0x00 || AC == 0x01)) || + (key == MfClassicKeyB && (AC == 0x04 || AC == 0x03))); + } + case MfClassicActionACRead: { + return ( + (key == MfClassicKeyA) || + (key == MfClassicKeyB && !(AC == 0x00 || AC == 0x02 || AC == 0x01))); + } + case MfClassicActionACWrite: { + return ( + (key == MfClassicKeyA && (AC == 0x01)) || + (key == MfClassicKeyB && (AC == 0x03 || AC == 0x05))); + } + default: + return false; + } + return true; +} + +static bool mf_classic_is_allowed_access_data_block( + MfClassicEmulator* emulator, + uint8_t block_num, + MfClassicKey key, + MfClassicAction action) { + uint8_t* sector_trailer = emulator->data.block[mf_classic_get_sector_trailer(block_num)].value; + + uint8_t sector_block; + if(block_num <= 128) { + sector_block = block_num & 0x03; + } else { + sector_block = (block_num & 0x0f) / 5; + } + + uint8_t AC; + switch(sector_block) { + case 0x00: { + AC = ((sector_trailer[7] >> 2) & 0x04) | ((sector_trailer[8] << 1) & 0x02) | + ((sector_trailer[8] >> 4) & 0x01); + break; + } + case 0x01: { + AC = ((sector_trailer[7] >> 3) & 0x04) | ((sector_trailer[8] >> 0) & 0x02) | + ((sector_trailer[8] >> 5) & 0x01); + break; + } + case 0x02: { + AC = ((sector_trailer[7] >> 4) & 0x04) | ((sector_trailer[8] >> 1) & 0x02) | + ((sector_trailer[8] >> 6) & 0x01); + break; + } + default: + return false; + } + + switch(action) { + case MfClassicActionDataRead: { + return ( + (key == MfClassicKeyA && !(AC == 0x03 || AC == 0x05 || AC == 0x07)) || + (key == MfClassicKeyB && !(AC == 0x07))); + } + case MfClassicActionDataWrite: { + return ( + (key == MfClassicKeyA && (AC == 0x00)) || + (key == MfClassicKeyB && (AC == 0x00 || AC == 0x04 || AC == 0x06 || AC == 0x03))); + } + case MfClassicActionDataInc: { + return ( + (key == MfClassicKeyA && (AC == 0x00)) || + (key == MfClassicKeyB && (AC == 0x00 || AC == 0x06))); + } + case MfClassicActionDataDec: { + return ( + (key == MfClassicKeyA && (AC == 0x00 || AC == 0x06 || AC == 0x01)) || + (key == MfClassicKeyB && (AC == 0x00 || AC == 0x06 || AC == 0x01))); + } + default: + return false; + } + + return false; +} + +static bool mf_classic_is_allowed_access( + MfClassicEmulator* emulator, + uint8_t block_num, + MfClassicKey key, + MfClassicAction action) { + if(mf_classic_is_sector_trailer(block_num)) { + return mf_classic_is_allowed_access_sector_trailer(emulator, block_num, key, action); + } else { + return mf_classic_is_allowed_access_data_block(emulator, block_num, key, action); + } +} + bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { UNUSED(ATQA1); if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08)) { @@ -120,7 +281,7 @@ static bool mf_classic_auth( tx_rx->tx_data[1] = block; tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRxNoCrc; tx_rx->tx_bits = 2 * 8; - if(!furi_hal_nfc_tx_rx(tx_rx, 5)) break; + if(!furi_hal_nfc_tx_rx(tx_rx, 6)) break; uint32_t nt = (uint32_t)nfc_util_bytes2num(tx_rx->rx_data, 4); crypto1_init(crypto, key); @@ -142,7 +303,7 @@ static bool mf_classic_auth( } tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; tx_rx->tx_bits = 8 * 8; - if(!furi_hal_nfc_tx_rx(tx_rx, 5)) break; + if(!furi_hal_nfc_tx_rx(tx_rx, 6)) break; if(tx_rx->rx_bits == 32) { crypto1_word(crypto, 0, 0); auth_success = true; @@ -296,6 +457,8 @@ uint8_t mf_classic_read_card( uint8_t sectors_read = 0; data->type = reader->type; + data->key_a_mask = 0; + data->key_b_mask = 0; MfClassicSector temp_sector = {}; for(uint8_t i = 0; i < reader->sectors_to_read; i++) { if(mf_classic_read_sector( @@ -305,9 +468,279 @@ uint8_t mf_classic_read_card( for(uint8_t j = 0; j < temp_sector.total_blocks; j++) { data->block[first_block + j] = temp_sector.block[j]; } + if(reader->sector_reader[i].key_a != MF_CLASSIC_NO_KEY) { + data->key_a_mask |= 1 << reader->sector_reader[i].sector_num; + } + if(reader->sector_reader[i].key_b != MF_CLASSIC_NO_KEY) { + data->key_b_mask |= 1 << reader->sector_reader[i].sector_num; + } sectors_read++; } } return sectors_read; } + +void mf_crypto1_decrypt( + Crypto1* crypto, + uint8_t* encrypted_data, + uint16_t encrypted_data_bits, + uint8_t* decrypted_data) { + if(encrypted_data_bits < 8) { + uint8_t decrypted_byte = 0; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 0)) << 0; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 1)) << 1; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 2)) << 2; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 3)) << 3; + decrypted_data[0] = decrypted_byte; + } else { + for(size_t i = 0; i < encrypted_data_bits / 8; i++) { + decrypted_data[i] = crypto1_byte(crypto, 0, 0) ^ encrypted_data[i]; + } + } +} + +void mf_crypto1_encrypt( + Crypto1* crypto, + uint8_t* keystream, + uint8_t* plain_data, + uint16_t plain_data_bits, + uint8_t* encrypted_data, + uint8_t* encrypted_parity) { + if(plain_data_bits < 8) { + encrypted_data[0] = 0; + for(size_t i = 0; i < plain_data_bits; i++) { + encrypted_data[0] |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(plain_data[0], i)) << i; + } + } else { + memset(encrypted_parity, 0, plain_data_bits / 8 + 1); + for(uint8_t i = 0; i < plain_data_bits / 8; i++) { + encrypted_data[i] = crypto1_byte(crypto, keystream ? keystream[i] : 0, 0) ^ + plain_data[i]; + encrypted_parity[i / 8] |= + (((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(plain_data[i])) & 0x01) + << (7 - (i & 0x0007))); + } + } +} + +bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_rx) { + furi_assert(emulator); + furi_assert(tx_rx); + bool command_processed = false; + bool is_encrypted = false; + uint8_t plain_data[MF_CLASSIC_MAX_DATA_SIZE]; + MfClassicKey access_key = MfClassicKeyA; + + // Read command + while(!command_processed) { + if(!is_encrypted) { + // Read first frame + tx_rx->tx_bits = 0; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + } + if(!furi_hal_nfc_tx_rx(tx_rx, 300)) { + FURI_LOG_D( + TAG, "Error in tx rx. Tx :%d bits, Rx: %d bits", tx_rx->tx_bits, tx_rx->rx_bits); + break; + } + if(!is_encrypted) { + memcpy(plain_data, tx_rx->rx_data, tx_rx->rx_bits / 8); + } else { + mf_crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); + } + // TODO Check crc + + if(plain_data[0] == 0x50 && plain_data[1] == 00) { + FURI_LOG_T(TAG, "Halt received"); + command_processed = true; + break; + } else if(plain_data[0] == 0x60 || plain_data[0] == 0x61) { + uint8_t block = plain_data[1]; + uint64_t key = 0; + uint8_t sector_trailer_block = mf_classic_get_sector_trailer(block); + MfClassicSectorTrailer* sector_trailer = + (MfClassicSectorTrailer*)emulator->data.block[sector_trailer_block].value; + if(plain_data[0] == 0x61) { + key = nfc_util_bytes2num(sector_trailer->key_b, 6); + access_key = MfClassicKeyA; + } else { + key = nfc_util_bytes2num(sector_trailer->key_a, 6); + access_key = MfClassicKeyB; + } + + uint32_t nonce = prng_successor(DWT->CYCCNT, 32); + uint8_t nt[4]; + uint8_t nt_keystream[4]; + nfc_util_num2bytes(nonce, 4, nt); + nfc_util_num2bytes(nonce ^ emulator->cuid, 4, nt_keystream); + crypto1_init(&emulator->crypto, key); + if(!is_encrypted) { + crypto1_word(&emulator->crypto, emulator->cuid ^ nonce, 0); + memcpy(tx_rx->tx_data, nt, sizeof(nt)); + tx_rx->tx_bits = sizeof(nt) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRxRaw; + } else { + mf_crypto1_encrypt( + &emulator->crypto, + nt_keystream, + nt, + sizeof(nt) * 8, + tx_rx->tx_data, + tx_rx->tx_parity); + tx_rx->tx_bits = sizeof(nt) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + } + if(!furi_hal_nfc_tx_rx(tx_rx, 500)) { + FURI_LOG_E(TAG, "Error in NT exchange"); + command_processed = true; + break; + } + + if(tx_rx->rx_bits != 64) { + FURI_LOG_W(TAG, "Incorrect nr + ar"); + command_processed = true; + break; + } + + // Check if we store valid key + if(access_key == MfClassicKeyA) { + if(FURI_BIT(emulator->data.key_a_mask, mf_classic_get_sector_by_block(block)) == + 0) { + FURI_LOG_D(TAG, "Unsupported sector key A for block %d", sector_trailer_block); + break; + } + } else if(access_key == MfClassicKeyB) { + if(FURI_BIT(emulator->data.key_b_mask, mf_classic_get_sector_by_block(block)) == + 0) { + FURI_LOG_D(TAG, "Unsupported sector key B for block %d", sector_trailer_block); + break; + } + } + + uint32_t nr = nfc_util_bytes2num(tx_rx->rx_data, 4); + uint32_t ar = nfc_util_bytes2num(&tx_rx->rx_data[4], 4); + crypto1_word(&emulator->crypto, nr, 1); + uint32_t cardRr = ar ^ crypto1_word(&emulator->crypto, 0, 0); + if(cardRr != prng_successor(nonce, 64)) { + FURI_LOG_T(TAG, "Wrong AUTH! %08X != %08X", cardRr, prng_successor(nonce, 64)); + // Don't send NACK, as tag don't send it + command_processed = true; + break; + } + + uint32_t ans = prng_successor(nonce, 96); + uint8_t responce[4] = {}; + nfc_util_num2bytes(ans, 4, responce); + mf_crypto1_encrypt( + &emulator->crypto, + NULL, + responce, + sizeof(responce) * 8, + tx_rx->tx_data, + tx_rx->tx_parity); + tx_rx->tx_bits = sizeof(responce) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + + is_encrypted = true; + } else if(is_encrypted && plain_data[0] == 0x30) { + uint8_t block = plain_data[1]; + uint8_t block_data[18] = {}; + memcpy(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE); + if(mf_classic_is_sector_trailer(block)) { + if(!mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionKeyARead)) { + memset(block_data, 0, 6); + } + if(!mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionKeyBRead)) { + memset(&block_data[10], 0, 6); + } + if(!mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionACRead)) { + memset(&block_data[6], 0, 4); + } + } else { + if(!mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionDataRead)) { + memset(block_data, 0, 16); + } + } + nfca_append_crc16(block_data, 16); + + mf_crypto1_encrypt( + &emulator->crypto, + NULL, + block_data, + sizeof(block_data) * 8, + tx_rx->tx_data, + tx_rx->tx_parity); + tx_rx->tx_bits = 18 * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + } else if(is_encrypted && plain_data[0] == 0xA0) { + uint8_t block = plain_data[1]; + if(block > mf_classic_get_total_block_num(emulator->data.type)) { + break; + } + // Send ACK + uint8_t ack = 0x0A; + mf_crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity); + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + tx_rx->tx_bits = 4; + + if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break; + if(tx_rx->rx_bits != 18 * 8) break; + + mf_crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); + uint8_t block_data[16] = {}; + memcpy(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE); + if(mf_classic_is_sector_trailer(block)) { + if(mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionKeyAWrite)) { + memcpy(block_data, plain_data, 6); + } + if(mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionKeyBWrite)) { + memcpy(&block_data[10], &plain_data[10], 6); + } + if(mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionACWrite)) { + memcpy(&block_data[6], &plain_data[6], 4); + } + } else { + if(mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionDataWrite)) { + memcpy(block_data, plain_data, MF_CLASSIC_BLOCK_SIZE); + } + } + if(memcmp(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE)) { + memcpy(emulator->data.block[block].value, block_data, MF_CLASSIC_BLOCK_SIZE); + emulator->data_changed = true; + } + // Send ACK + ack = 0x0A; + mf_crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity); + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + tx_rx->tx_bits = 4; + } else { + // Unknown command + break; + } + } + + if(!command_processed) { + // Send NACK + uint8_t nack = 0x04; + if(is_encrypted) { + mf_crypto1_encrypt( + &emulator->crypto, NULL, &nack, 4, tx_rx->tx_data, tx_rx->tx_parity); + } else { + tx_rx->tx_data[0] = nack; + } + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + tx_rx->tx_bits = 4; + furi_hal_nfc_tx_rx(tx_rx, 300); + } + + return true; +} diff --git a/lib/nfc_protocols/mifare_classic.h b/lib/nfc_protocols/mifare_classic.h index fa778b77..bbf34b2d 100644 --- a/lib/nfc_protocols/mifare_classic.h +++ b/lib/nfc_protocols/mifare_classic.h @@ -13,6 +13,7 @@ #define MF_CLASSIC_BLOCKS_IN_SECTOR_MAX (16) #define MF_CLASSIC_NO_KEY (0xFFFFFFFFFFFFFFFF) +#define MF_CLASSIC_MAX_DATA_SIZE (16) typedef enum { MfClassicType1k, @@ -41,6 +42,8 @@ typedef struct { typedef struct { MfClassicType type; + uint64_t key_a_mask; + uint64_t key_b_mask; MfClassicBlock block[MF_CLASSIC_TOTAL_BLOCKS_MAX]; } MfClassicData; @@ -65,6 +68,13 @@ typedef struct { MfClassicSectorReader sector_reader[MF_CLASSIC_SECTORS_MAX]; } MfClassicReader; +typedef struct { + uint32_t cuid; + Crypto1 crypto; + MfClassicData data; + bool data_changed; +} MfClassicEmulator; + bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); bool mf_classic_get_type( @@ -100,3 +110,5 @@ uint8_t mf_classic_read_card( FuriHalNfcTxRxContext* tx_rx, MfClassicReader* reader, MfClassicData* data); + +bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_rx); diff --git a/lib/nfc_protocols/mifare_desfire.c b/lib/nfc_protocols/mifare_desfire.c index 4f02e839..6f28dc5d 100644 --- a/lib/nfc_protocols/mifare_desfire.c +++ b/lib/nfc_protocols/mifare_desfire.c @@ -115,6 +115,9 @@ void mf_df_cat_key_settings(MifareDesfireKeySettings* ks, string_t out) { string_cat_printf(out, "freeCreateDelete %d\n", ks->free_create_delete); string_cat_printf(out, "freeDirectoryList %d\n", ks->free_directory_list); string_cat_printf(out, "masterChangeable %d\n", ks->master_key_changeable); + if(ks->flags) { + string_cat_printf(out, "flags %d\n", ks->flags); + } string_cat_printf(out, "maxKeys %d\n", ks->max_keys); for(MifareDesfireKeyVersion* kv = ks->key_version_head; kv; kv = kv->next) { string_cat_printf(out, "key %d version %d\n", kv->id, kv->version); @@ -274,7 +277,8 @@ bool mf_df_parse_get_key_settings_response( out->free_create_delete = (buf[0] & 0x4) != 0; out->free_directory_list = (buf[0] & 0x2) != 0; out->master_key_changeable = (buf[0] & 0x1) != 0; - out->max_keys = buf[1]; + out->flags = buf[1] >> 4; + out->max_keys = buf[1] & 0xF; return true; } diff --git a/lib/nfc_protocols/mifare_desfire.h b/lib/nfc_protocols/mifare_desfire.h index 809f1782..dbe0802e 100644 --- a/lib/nfc_protocols/mifare_desfire.h +++ b/lib/nfc_protocols/mifare_desfire.h @@ -56,6 +56,7 @@ typedef struct { bool free_create_delete; bool free_directory_list; bool master_key_changeable; + uint8_t flags; uint8_t max_keys; MifareDesfireKeyVersion* key_version_head; } MifareDesfireKeySettings; diff --git a/lib/nfc_protocols/mifare_ultralight.c b/lib/nfc_protocols/mifare_ultralight.c index 5b3d70b5..fb425ea7 100644 --- a/lib/nfc_protocols/mifare_ultralight.c +++ b/lib/nfc_protocols/mifare_ultralight.c @@ -1,5 +1,7 @@ +#include #include "mifare_ultralight.h" #include +#include #define TAG "MfUltralight" @@ -16,6 +18,7 @@ static void mf_ul_set_default_version(MfUltralightReader* reader, MfUltralightDa reader->support_fast_read = false; reader->support_tearing_flags = false; reader->support_counters = false; + reader->support_signature = false; } bool mf_ultralight_read_version( @@ -44,30 +47,71 @@ bool mf_ultralight_read_version( reader->support_fast_read = true; reader->support_tearing_flags = true; reader->support_counters = true; + reader->support_signature = true; } else if(version->storage_size == 0x0E) { data->type = MfUltralightTypeUL21; reader->pages_to_read = 41; reader->support_fast_read = true; reader->support_tearing_flags = true; reader->support_counters = true; + reader->support_signature = true; } else if(version->storage_size == 0x0F) { data->type = MfUltralightTypeNTAG213; reader->pages_to_read = 45; reader->support_fast_read = true; reader->support_tearing_flags = false; reader->support_counters = false; + reader->support_signature = true; } else if(version->storage_size == 0x11) { data->type = MfUltralightTypeNTAG215; reader->pages_to_read = 135; reader->support_fast_read = true; reader->support_tearing_flags = false; reader->support_counters = false; + reader->support_signature = true; + } else if(version->prod_subtype == 5 && version->prod_ver_major == 2) { + // NTAG I2C + bool known = false; + if(version->prod_ver_minor == 1) { + if(version->storage_size == 0x13) { + data->type = MfUltralightTypeNTAGI2C1K; + reader->pages_to_read = 231; + reader->support_signature = false; + known = true; + } else if(version->storage_size == 0x15) { + data->type = MfUltralightTypeNTAGI2C2K; + reader->pages_to_read = 485; + reader->support_signature = false; + known = true; + } + } else if(version->prod_ver_minor == 2) { + if(version->storage_size == 0x13) { + data->type = MfUltralightTypeNTAGI2CPlus1K; + reader->pages_to_read = 236; + reader->support_signature = true; + known = true; + } else if(version->storage_size == 0x15) { + data->type = MfUltralightTypeNTAGI2CPlus2K; + reader->pages_to_read = 492; + reader->support_signature = true; + known = true; + } + } + + if(known) { + reader->support_fast_read = true; + reader->support_tearing_flags = false; + reader->support_counters = false; + } else { + mf_ul_set_default_version(reader, data); + } } else if(version->storage_size == 0x13) { data->type = MfUltralightTypeNTAG216; reader->pages_to_read = 231; reader->support_fast_read = true; reader->support_tearing_flags = false; reader->support_counters = false; + reader->support_signature = true; } else { mf_ul_set_default_version(reader, data); break; @@ -78,26 +122,351 @@ bool mf_ultralight_read_version( return version_read; } +static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag_1k( + int16_t linear_address, + uint8_t* sector, + int16_t* valid_pages) { + // 0 - 226: sector 0 + // 227 - 228: config registers + // 229 - 230: session registers + + if(linear_address > 230) { + *valid_pages = 0; + return -1; + } else if(linear_address >= 229) { + *sector = 3; + *valid_pages = 2 - (linear_address - 229); + return linear_address - 229 + 248; + } else if(linear_address >= 227) { + *sector = 0; + *valid_pages = 2 - (linear_address - 227); + return linear_address - 227 + 232; + } else { + *sector = 0; + *valid_pages = 227 - linear_address; + return linear_address; + } +} + +static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag_2k( + int16_t linear_address, + uint8_t* sector, + int16_t* valid_pages) { + // 0 - 255: sector 0 + // 256 - 480: sector 1 + // 481 - 482: config registers + // 483 - 484: session registers + + if(linear_address > 484) { + *valid_pages = 0; + return -1; + } else if(linear_address >= 483) { + *sector = 3; + *valid_pages = 2 - (linear_address - 483); + return linear_address - 483 + 248; + } else if(linear_address >= 481) { + *sector = 1; + *valid_pages = 2 - (linear_address - 481); + return linear_address - 481 + 232; + } else if(linear_address >= 256) { + *sector = 1; + *valid_pages = 225 - (linear_address - 256); + return linear_address - 256; + } else { + *sector = 0; + *valid_pages = 256 - linear_address; + return linear_address; + } +} + +static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag_plus_1k( + int16_t linear_address, + uint8_t* sector, + int16_t* valid_pages) { + // 0 - 233: sector 0 + registers + // 234 - 235: session registers + + if(linear_address > 235) { + *valid_pages = 0; + return -1; + } else if(linear_address >= 234) { + *sector = 0; + *valid_pages = 2 - (linear_address - 234); + return linear_address - 234 + 236; + } else { + *sector = 0; + *valid_pages = 234 - linear_address; + return linear_address; + } +} + +static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag_plus_2k( + int16_t linear_address, + uint8_t* sector, + int16_t* valid_pages) { + // 0 - 233: sector 0 + registers + // 234 - 235: session registers + // 236 - 491: sector 1 + + if(linear_address > 491) { + *valid_pages = 0; + return -1; + } else if(linear_address >= 236) { + *sector = 1; + *valid_pages = 256 - (linear_address - 236); + return linear_address - 236; + } else if(linear_address >= 234) { + *sector = 0; + *valid_pages = 2 - (linear_address - 234); + return linear_address - 234 + 236; + } else { + *sector = 0; + *valid_pages = 234 - linear_address; + return linear_address; + } +} + +static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag( + MfUltralightData* data, + MfUltralightReader* reader, + int16_t linear_address, + uint8_t* sector, + int16_t* valid_pages) { + switch(data->type) { + case MfUltralightTypeNTAGI2C1K: + return mf_ultralight_ntag_i2c_addr_lin_to_tag_1k(linear_address, sector, valid_pages); + + case MfUltralightTypeNTAGI2C2K: + return mf_ultralight_ntag_i2c_addr_lin_to_tag_2k(linear_address, sector, valid_pages); + + case MfUltralightTypeNTAGI2CPlus1K: + return mf_ultralight_ntag_i2c_addr_lin_to_tag_plus_1k(linear_address, sector, valid_pages); + + case MfUltralightTypeNTAGI2CPlus2K: + return mf_ultralight_ntag_i2c_addr_lin_to_tag_plus_2k(linear_address, sector, valid_pages); + + default: + *sector = 0xff; + *valid_pages = reader->pages_to_read - linear_address; + return linear_address; + } +} + +static int16_t + mf_ultralight_ntag_i2c_addr_tag_to_lin_1k(uint8_t page, uint8_t sector, uint16_t* valid_pages) { + bool valid = false; + int16_t translated_page; + if(sector == 0) { + if(page <= 226) { + *valid_pages = 227 - page; + translated_page = page; + valid = true; + } else if(page >= 232 && page <= 233) { + *valid_pages = 2 - (page - 232); + translated_page = page - 232 + 227; + valid = true; + } + } else if(sector == 3) { + if(page >= 248 && page <= 249) { + *valid_pages = 2 - (page - 248); + translated_page = page - 248 + 229; + valid = true; + } + } + + if(!valid) { + *valid_pages = 0; + translated_page = -1; + } + return translated_page; +} + +static int16_t + mf_ultralight_ntag_i2c_addr_tag_to_lin_2k(uint8_t page, uint8_t sector, uint16_t* valid_pages) { + bool valid = false; + int16_t translated_page; + if(sector == 0) { + *valid_pages = 256 - page; + translated_page = page; + valid = true; + } else if(sector == 1) { + if(page <= 224) { + *valid_pages = 225 - page; + translated_page = 256 + page; + valid = true; + } else if(page >= 232 && page <= 233) { + *valid_pages = 2 - (page - 232); + translated_page = page - 232 + 481; + valid = true; + } + } else if(sector == 3) { + if(page >= 248 && page <= 249) { + *valid_pages = 2 - (page - 248); + translated_page = page - 248 + 483; + valid = true; + } + } + + if(!valid) { + *valid_pages = 0; + translated_page = -1; + } + return translated_page; +} + +static int16_t mf_ultralight_ntag_i2c_addr_tag_to_lin_plus_1k( + uint8_t page, + uint8_t sector, + uint16_t* valid_pages) { + bool valid = false; + int16_t translated_page; + if(sector == 0) { + if(page <= 233) { + *valid_pages = 234 - page; + translated_page = page; + valid = true; + } else if(page >= 236 && page <= 237) { + *valid_pages = 2 - (page - 236); + translated_page = page - 236 + 234; + valid = true; + } + } else if(sector == 3) { + if(page >= 248 && page <= 249) { + *valid_pages = 2 - (page - 248); + translated_page = page - 248 + 234; + valid = true; + } + } + + if(!valid) { + *valid_pages = 0; + translated_page = -1; + } + return translated_page; +} + +static int16_t mf_ultralight_ntag_i2c_addr_tag_to_lin_plus_2k( + uint8_t page, + uint8_t sector, + uint16_t* valid_pages) { + bool valid = false; + int16_t translated_page; + if(sector == 0) { + if(page <= 233) { + *valid_pages = 234 - page; + translated_page = page; + valid = true; + } else if(page >= 236 && page <= 237) { + *valid_pages = 2 - (page - 236); + translated_page = page - 236 + 234; + valid = true; + } + } else if(sector == 1) { + *valid_pages = 256 - page; + translated_page = page + 236; + valid = true; + } else if(sector == 3) { + if(page >= 248 && page <= 249) { + *valid_pages = 2 - (page - 248); + translated_page = page - 248 + 234; + valid = true; + } + } + + if(!valid) { + *valid_pages = 0; + translated_page = -1; + } + return translated_page; +} + +static int16_t mf_ultralight_ntag_i2c_addr_tag_to_lin( + MfUltralightData* data, + uint8_t page, + uint8_t sector, + uint16_t* valid_pages) { + switch(data->type) { + case MfUltralightTypeNTAGI2C1K: + return mf_ultralight_ntag_i2c_addr_tag_to_lin_1k(page, sector, valid_pages); + + case MfUltralightTypeNTAGI2C2K: + return mf_ultralight_ntag_i2c_addr_tag_to_lin_2k(page, sector, valid_pages); + + case MfUltralightTypeNTAGI2CPlus1K: + return mf_ultralight_ntag_i2c_addr_tag_to_lin_plus_1k(page, sector, valid_pages); + + case MfUltralightTypeNTAGI2CPlus2K: + return mf_ultralight_ntag_i2c_addr_tag_to_lin_plus_2k(page, sector, valid_pages); + + default: + *valid_pages = data->data_size / 4 - page; + return page; + } +} + +static bool mf_ultralight_sector_select(FuriHalNfcTxRxContext* tx_rx, uint8_t sector) { + FURI_LOG_D(TAG, "Selecting sector %u", sector); + tx_rx->tx_data[0] = MF_UL_SECTOR_SELECT; + tx_rx->tx_data[1] = 0xff; + tx_rx->tx_bits = 16; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + if(!furi_hal_nfc_tx_rx(tx_rx, 50)) { + FURI_LOG_D(TAG, "Failed to issue sector select command"); + return false; + } + + tx_rx->tx_data[0] = sector; + tx_rx->tx_data[1] = 0x00; + tx_rx->tx_data[2] = 0x00; + tx_rx->tx_data[3] = 0x00; + tx_rx->tx_bits = 32; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + // This is NOT a typo! The tag ACKs by not sending a response within 1ms. + if(furi_hal_nfc_tx_rx(tx_rx, 20)) { + // TODO: what gets returned when an actual NAK is received? + FURI_LOG_D(TAG, "Sector %u select NAK'd", sector); + return false; + } + + return true; +} + bool mf_ultralight_read_pages( FuriHalNfcTxRxContext* tx_rx, MfUltralightReader* reader, MfUltralightData* data) { uint8_t pages_read_cnt = 0; + uint8_t curr_sector_index = 0xff; + reader->pages_read = 0; + for(size_t i = 0; i < reader->pages_to_read; i += pages_read_cnt) { + uint8_t tag_sector; + int16_t valid_pages; + int16_t tag_page = mf_ultralight_ntag_i2c_addr_lin_to_tag( + data, reader, (int16_t)i, &tag_sector, &valid_pages); - for(size_t i = 0; i < reader->pages_to_read; i += 4) { - FURI_LOG_D(TAG, "Reading pages %d - %d", i, i + 3); + furi_assert(tag_page != -1); + if(curr_sector_index != tag_sector) { + if(!mf_ultralight_sector_select(tx_rx, tag_sector)) return false; + curr_sector_index = tag_sector; + } + + FURI_LOG_D(TAG, "Reading pages %d - %d", i, i + (valid_pages > 4 ? 4 : valid_pages) - 1); tx_rx->tx_data[0] = MF_UL_READ_CMD; - tx_rx->tx_data[1] = i; + tx_rx->tx_data[1] = tag_page; tx_rx->tx_bits = 16; tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; if(!furi_hal_nfc_tx_rx(tx_rx, 50)) { - FURI_LOG_D(TAG, "Failed to read pages %d - %d", i, i + 3); + FURI_LOG_D( + TAG, + "Failed to read pages %d - %d", + i, + i + (valid_pages > 4 ? 4 : valid_pages) - 1); break; } - if(i + 4 <= reader->pages_to_read) { + if(valid_pages > 4) { pages_read_cnt = 4; } else { - pages_read_cnt = reader->pages_to_read - reader->pages_read; + pages_read_cnt = valid_pages; } reader->pages_read += pages_read_cnt; data->data_size = reader->pages_read * 4; @@ -111,18 +480,39 @@ bool mf_ultralight_fast_read_pages( FuriHalNfcTxRxContext* tx_rx, MfUltralightReader* reader, MfUltralightData* data) { - FURI_LOG_D(TAG, "Reading pages 0 - %d", reader->pages_to_read); - tx_rx->tx_data[0] = MF_UL_FAST_READ_CMD; - tx_rx->tx_data[1] = 0; - tx_rx->tx_data[2] = reader->pages_to_read - 1; - tx_rx->tx_bits = 24; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - if(furi_hal_nfc_tx_rx(tx_rx, 50)) { - reader->pages_read = reader->pages_to_read; - data->data_size = reader->pages_read * 4; - memcpy(data->data, tx_rx->rx_data, data->data_size); - } else { - FURI_LOG_D(TAG, "Failed to read pages 0 - %d", reader->pages_to_read); + uint8_t curr_sector_index = 0xff; + reader->pages_read = 0; + while(reader->pages_read < reader->pages_to_read) { + uint8_t tag_sector; + int16_t valid_pages; + int16_t tag_page = mf_ultralight_ntag_i2c_addr_lin_to_tag( + data, reader, reader->pages_read, &tag_sector, &valid_pages); + + furi_assert(tag_page != -1); + if(curr_sector_index != tag_sector) { + if(!mf_ultralight_sector_select(tx_rx, tag_sector)) return false; + curr_sector_index = tag_sector; + } + + FURI_LOG_D( + TAG, "Reading pages %d - %d", reader->pages_read, reader->pages_read + valid_pages - 1); + tx_rx->tx_data[0] = MF_UL_FAST_READ_CMD; + tx_rx->tx_data[1] = tag_page; + tx_rx->tx_data[2] = valid_pages - 1; + tx_rx->tx_bits = 24; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + if(furi_hal_nfc_tx_rx(tx_rx, 50)) { + memcpy(&data->data[reader->pages_read * 4], tx_rx->rx_data, valid_pages * 4); + reader->pages_read += valid_pages; + data->data_size = reader->pages_read * 4; + } else { + FURI_LOG_D( + TAG, + "Failed to read pages %d - %d", + reader->pages_read, + reader->pages_read + valid_pages - 1); + break; + } } return reader->pages_read == reader->pages_to_read; @@ -199,8 +589,10 @@ bool mf_ul_read_card( // Read Mifare Ultralight version if(mf_ultralight_read_version(tx_rx, reader, data)) { - // Read Signature - mf_ultralight_read_signature(tx_rx, data); + if(reader->support_signature) { + // Read Signature + mf_ultralight_read_signature(tx_rx, data); + } } if(reader->support_counters) { mf_ultralight_read_counters(tx_rx, data); @@ -231,11 +623,123 @@ static void mf_ul_protect_auth_data_on_read_command( } } +static void mf_ul_protect_auth_data_on_read_command_i2c( + uint8_t* tx_buff, + uint8_t start_page, + uint8_t end_page, + MfUltralightEmulator* emulator) { + if(emulator->data.type >= MfUltralightTypeNTAGI2CPlus1K) { + // Blank out PWD and PACK + if(start_page <= 229 && end_page >= 229) { + uint16_t offset = (229 - start_page) * 4; + uint8_t count = 4; + if(end_page >= 230) count += 2; + memset(&tx_buff[offset], 0, count); + } + + // Handle AUTH0 for sector 0 + if(!emulator->auth_success) { + uint8_t access = emulator->data.data[228 * 4]; + if(access & 0x80) { + uint8_t auth0 = emulator->data.data[227 * 4 + 3]; + if(auth0 < end_page) { + // start_page is always < auth0; otherwise is NAK'd already + uint8_t page_offset = auth0 - start_page; + uint8_t page_count = end_page - auth0; + memset(&tx_buff[page_offset * 4], 0, page_count * 4); + } + } + } + } +} + +static void mf_ul_ntag_i2c_fill_cross_area_read( + uint8_t* tx_buff, + uint8_t start_page, + uint8_t end_page, + MfUltralightEmulator* emulator) { + // For copying config or session registers in fast read + int16_t tx_page_offset; + int16_t data_page_offset; + uint8_t page_length; + bool apply = false; + MfUltralightType type = emulator->data.type; + if(emulator->curr_sector == 0) { + if(type == MfUltralightTypeNTAGI2C1K) { + if(start_page <= 233 && end_page >= 232) { + tx_page_offset = start_page - 232; + data_page_offset = 227; + page_length = 2; + apply = true; + } + } else if(type == MfUltralightTypeNTAGI2CPlus1K || type == MfUltralightTypeNTAGI2CPlus2K) { + if(start_page <= 237 && end_page >= 236) { + tx_page_offset = start_page - 236; + data_page_offset = 234; + page_length = 2; + apply = true; + } + } + } else if(emulator->curr_sector == 1) { + if(type == MfUltralightTypeNTAGI2C2K) { + if(start_page <= 233 && end_page >= 232) { + tx_page_offset = start_page - 232; + data_page_offset = 483; + page_length = 2; + apply = true; + } + } + } + + if(apply) { + while(tx_page_offset < 0 && page_length > 0) { + ++tx_page_offset; + ++data_page_offset; + --page_length; + } + memcpy( + &tx_buff[tx_page_offset * 4], + &emulator->data.data[data_page_offset * 4], + page_length * 4); + } +} + +static bool mf_ul_ntag_i2c_plus_check_auth( + MfUltralightEmulator* emulator, + uint8_t start_page, + bool is_write) { + if(!emulator->auth_success) { + uint8_t access = emulator->data.data[228 * 4]; + // Check NFC_PROT + if(emulator->curr_sector == 0 && ((access & 0x80) || is_write)) { + uint8_t auth0 = emulator->data.data[227 * 4 + 3]; + if(start_page >= auth0) return false; + } else if(emulator->curr_sector == 1) { + // We don't have to specifically check for type because this is done + // by address translator + uint8_t pt_i2c = emulator->data.data[231 * 4]; + // Check 2K_PROT + if(pt_i2c & 0x08) return false; + } + } + + if(emulator->curr_sector == 1) { + // Check NFC_DIS_SEC1 + uint8_t access = emulator->data.data[228 * 4]; + if(access & 0x20) return false; + } + + return true; +} + void mf_ul_prepare_emulation(MfUltralightEmulator* emulator, MfUltralightData* data) { + FURI_LOG_D(TAG, "Prepare emulation"); emulator->data = *data; emulator->auth_data = NULL; emulator->data_changed = false; emulator->comp_write_cmd_started = false; + emulator->sector_select_cmd_started = false; + emulator->ntag_i2c_plus_sector3_lockout = false; if(data->type == MfUltralightTypeUnknown) { emulator->support_fast_read = false; } else if(data->type == MfUltralightTypeUL11) { @@ -248,11 +752,15 @@ void mf_ul_prepare_emulation(MfUltralightEmulator* emulator, MfUltralightData* d emulator->support_fast_read = true; } else if(data->type == MfUltralightTypeNTAG216) { emulator->support_fast_read = true; + } else if(data->type >= MfUltralightTypeNTAGI2C1K) { + emulator->support_fast_read = true; } - if(data->type >= MfUltralightTypeNTAG213) { + if(data->type >= MfUltralightTypeNTAG213 && data->type < MfUltralightTypeNTAGI2C1K) { uint16_t pwd_page = (data->data_size / 4) - 2; emulator->auth_data = (MfUltralightAuth*)&data->data[pwd_page * 4]; + } else if(data->type >= MfUltralightTypeNTAGI2CPlus1K) { + emulator->auth_data = (MfUltralightAuth*)&data->data[229 * 4]; } } @@ -270,6 +778,19 @@ bool mf_ul_prepare_emulation_response( uint16_t tx_bytes = 0; uint16_t tx_bits = 0; bool command_parsed = false; + bool send_ack = false; + bool respond_nothing = false; + +#ifdef FURI_DEBUG + string_t debug_buf; + string_init(debug_buf); + for(int i = 0; i < (buff_rx_len + 7) / 8; ++i) { + string_cat_printf(debug_buf, "%02x ", buff_rx[i]); + } + string_strim(debug_buf); + FURI_LOG_T(TAG, "Emu RX (%d): %s", buff_rx_len, string_get_cstr(debug_buf)); + string_reset(debug_buf); +#endif // Check composite commands if(emulator->comp_write_cmd_started) { @@ -284,6 +805,15 @@ bool mf_ul_prepare_emulation_response( command_parsed = true; } emulator->comp_write_cmd_started = false; + } else if(emulator->sector_select_cmd_started) { + if(buff_rx[0] <= 0xFE) { + emulator->curr_sector = buff_rx[0] > 3 ? 0 : buff_rx[0]; + emulator->ntag_i2c_plus_sector3_lockout = false; + command_parsed = true; + respond_nothing = true; + FURI_LOG_D(TAG, "Changing sector to %d", emulator->curr_sector); + } + emulator->sector_select_cmd_started = false; } else if(cmd == MF_UL_GET_VERSION_CMD) { if(emulator->data.type != MfUltralightTypeUnknown) { tx_bytes = sizeof(emulator->data.version); @@ -292,85 +822,189 @@ bool mf_ul_prepare_emulation_response( command_parsed = true; } } else if(cmd == MF_UL_READ_CMD) { - uint8_t start_page = buff_rx[1]; - if(start_page < page_num) { - tx_bytes = 16; - if(start_page + 4 > page_num) { - // Handle roll-over mechanism - uint8_t end_pages_num = page_num - start_page; - memcpy(buff_tx, &emulator->data.data[start_page * 4], end_pages_num * 4); - memcpy(&buff_tx[end_pages_num * 4], emulator->data.data, (4 - end_pages_num) * 4); - } else { - memcpy(buff_tx, &emulator->data.data[start_page * 4], tx_bytes); + int16_t start_page = buff_rx[1]; + tx_bytes = 16; + if(emulator->data.type < MfUltralightTypeNTAGI2C1K) { + if(start_page < page_num) { + if(start_page + 4 > page_num) { + // Handle roll-over mechanism + uint8_t end_pages_num = page_num - start_page; + memcpy(buff_tx, &emulator->data.data[start_page * 4], end_pages_num * 4); + memcpy( + &buff_tx[end_pages_num * 4], emulator->data.data, (4 - end_pages_num) * 4); + } else { + memcpy(buff_tx, &emulator->data.data[start_page * 4], tx_bytes); + } + mf_ul_protect_auth_data_on_read_command( + buff_tx, start_page, (start_page + 4), emulator); + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } + } else { + uint16_t valid_pages; + start_page = mf_ultralight_ntag_i2c_addr_tag_to_lin( + &emulator->data, start_page, emulator->curr_sector, &valid_pages); + if(start_page != -1) { + if(emulator->data.type < MfUltralightTypeNTAGI2CPlus1K || + mf_ul_ntag_i2c_plus_check_auth(emulator, buff_rx[1], false)) { + if(emulator->data.type >= MfUltralightTypeNTAGI2CPlus1K && + emulator->curr_sector == 3 && valid_pages == 1) { + // Rewind back a sector to match behavior on a real tag + --start_page; + ++valid_pages; + } + + uint16_t copy_count = (valid_pages > 4 ? 4 : valid_pages) * 4; + FURI_LOG_D( + TAG, + "NTAG I2C Emu: page valid, %02x:%02x -> %d, %d", + emulator->curr_sector, + buff_rx[1], + start_page, + valid_pages); + memcpy(buff_tx, &emulator->data.data[start_page * 4], copy_count); + // For NTAG I2C, there's no roll-over; remainder is filled by null bytes + if(copy_count < tx_bytes) + memset(&buff_tx[copy_count], 0, tx_bytes - copy_count); + // Special case: NTAG I2C Plus sector 0 page 233 read crosses into page 236 + if(start_page == 233) + memcpy(&buff_tx[12], &emulator->data.data[(start_page + 1) * 4], 4); + mf_ul_protect_auth_data_on_read_command_i2c( + buff_tx, start_page, start_page + copy_count / 4 - 1, emulator); + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } + } else { + FURI_LOG_D( + TAG, + "NTAG I2C Emu: page invalid, %02x:%02x", + emulator->curr_sector, + buff_rx[1]); + if(emulator->data.type >= MfUltralightTypeNTAGI2CPlus1K && + emulator->curr_sector == 3 && !emulator->ntag_i2c_plus_sector3_lockout) { + // NTAG I2C Plus has a weird behavior where if you read sector 3 + // at an invalid address, it responds with zeroes then locks + // the read out, while if you read the mirrored session registers, + // it returns both session registers on either pages + memset(buff_tx, 0, tx_bytes); + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + emulator->ntag_i2c_plus_sector3_lockout = true; + } } - mf_ul_protect_auth_data_on_read_command( - buff_tx, start_page, (start_page + 4), emulator); - *data_type = FURI_HAL_NFC_TXRX_DEFAULT; - command_parsed = true; } + if(!command_parsed) tx_bytes = 0; } else if(cmd == MF_UL_FAST_READ_CMD) { if(emulator->support_fast_read) { - uint8_t start_page = buff_rx[1]; + int16_t start_page = buff_rx[1]; uint8_t end_page = buff_rx[2]; - if((start_page < page_num) && (end_page < page_num) && (start_page < (end_page + 1))) { + if(start_page <= end_page) { tx_bytes = ((end_page + 1) - start_page) * 4; - memcpy(buff_tx, &emulator->data.data[start_page * 4], tx_bytes); - mf_ul_protect_auth_data_on_read_command(buff_tx, start_page, end_page, emulator); + if(emulator->data.type < MfUltralightTypeNTAGI2C1K) { + if((start_page < page_num) && (end_page < page_num)) { + memcpy(buff_tx, &emulator->data.data[start_page * 4], tx_bytes); + mf_ul_protect_auth_data_on_read_command( + buff_tx, start_page, end_page, emulator); + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } + } else { + uint16_t valid_pages; + start_page = mf_ultralight_ntag_i2c_addr_tag_to_lin( + &emulator->data, start_page, emulator->curr_sector, &valid_pages); + if(start_page != -1) { + if(emulator->data.type < MfUltralightTypeNTAGI2CPlus1K || + mf_ul_ntag_i2c_plus_check_auth(emulator, buff_rx[1], false)) { + uint16_t copy_count = (valid_pages > 4 ? 4 : valid_pages) * 4; + memcpy(buff_tx, &emulator->data.data[start_page * 4], copy_count); + if(copy_count < tx_bytes) + memset(&buff_tx[copy_count], 0, tx_bytes - copy_count); + mf_ul_ntag_i2c_fill_cross_area_read( + buff_tx, buff_rx[1], buff_rx[2], emulator); + mf_ul_protect_auth_data_on_read_command_i2c( + buff_tx, start_page, start_page + copy_count / 4 - 1, emulator); + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } + } + } + if(!command_parsed) tx_bytes = 0; + } + } + } else if(cmd == MF_UL_WRITE) { + int16_t write_page = buff_rx[1]; + if(write_page > 1) { + uint16_t valid_pages; + write_page = mf_ultralight_ntag_i2c_addr_tag_to_lin( + &emulator->data, write_page, emulator->curr_sector, &valid_pages); + if(write_page != -1 && + (emulator->data.type >= MfUltralightTypeNTAGI2C1K || (write_page < page_num - 2))) { + if(emulator->data.type < MfUltralightTypeNTAGI2CPlus1K || + mf_ul_ntag_i2c_plus_check_auth(emulator, buff_rx[1], true)) { + memcpy(&emulator->data.data[write_page * 4], &buff_rx[2], 4); + emulator->data_changed = true; + send_ack = true; + command_parsed = true; + } + } + } + } else if(cmd == MF_UL_FAST_WRITE) { + if(emulator->data.type >= MfUltralightTypeNTAGI2CPlus1K) { + if(buff_rx[1] == 0xF0 && buff_rx[2] == 0xFF) { + // TODO: update when SRAM emulation implemented + send_ack = true; + command_parsed = true; + } + } + } else if(cmd == MF_UL_COMP_WRITE) { + if(emulator->data.type < MfUltralightTypeNTAGI2C1K) { + uint8_t write_page = buff_rx[1]; + if((write_page > 1) && (write_page < page_num - 2)) { + emulator->comp_write_cmd_started = true; + emulator->comp_write_page_addr = write_page; + // ACK + buff_tx[0] = 0x0A; + tx_bits = 4; + *data_type = FURI_HAL_NFC_TXRX_RAW; + command_parsed = true; + } + } + } else if(cmd == MF_UL_READ_CNT) { + if(emulator->data.type < MfUltralightTypeNTAGI2C1K) { + uint8_t cnt_num = buff_rx[1]; + if(cnt_num < 3) { + buff_tx[0] = emulator->data.counter[cnt_num] >> 16; + buff_tx[1] = emulator->data.counter[cnt_num] >> 8; + buff_tx[2] = emulator->data.counter[cnt_num]; + tx_bytes = 3; *data_type = FURI_HAL_NFC_TXRX_DEFAULT; command_parsed = true; } } - } else if(cmd == MF_UL_WRITE) { - uint8_t write_page = buff_rx[1]; - if((write_page > 1) && (write_page < page_num - 2)) { - memcpy(&emulator->data.data[write_page * 4], &buff_rx[2], 4); - emulator->data_changed = true; - // ACK - buff_tx[0] = 0x0A; - tx_bits = 4; - *data_type = FURI_HAL_NFC_TXRX_RAW; - command_parsed = true; - } - } else if(cmd == MF_UL_COMP_WRITE) { - uint8_t write_page = buff_rx[1]; - if((write_page > 1) && (write_page < page_num - 2)) { - emulator->comp_write_cmd_started = true; - emulator->comp_write_page_addr = write_page; - // ACK - buff_tx[0] = 0x0A; - tx_bits = 4; - *data_type = FURI_HAL_NFC_TXRX_RAW; - command_parsed = true; - } - } else if(cmd == MF_UL_READ_CNT) { - uint8_t cnt_num = buff_rx[1]; - if(cnt_num < 3) { - buff_tx[0] = emulator->data.counter[cnt_num] >> 16; - buff_tx[1] = emulator->data.counter[cnt_num] >> 8; - buff_tx[2] = emulator->data.counter[cnt_num]; - tx_bytes = 3; - *data_type = FURI_HAL_NFC_TXRX_DEFAULT; - command_parsed = true; - } } else if(cmd == MF_UL_INC_CNT) { - uint8_t cnt_num = buff_rx[1]; - uint32_t inc = (buff_rx[2] | (buff_rx[3] << 8) | (buff_rx[4] << 16)); - if((cnt_num < 3) && (emulator->data.counter[cnt_num] + inc < 0x00FFFFFF)) { - emulator->data.counter[cnt_num] += inc; - emulator->data_changed = true; - // ACK - buff_tx[0] = 0x0A; - tx_bits = 4; - *data_type = FURI_HAL_NFC_TXRX_RAW; - command_parsed = true; + if(emulator->data.type < MfUltralightTypeNTAGI2C1K) { + uint8_t cnt_num = buff_rx[1]; + uint32_t inc = (buff_rx[2] | (buff_rx[3] << 8) | (buff_rx[4] << 16)); + if((cnt_num < 3) && (emulator->data.counter[cnt_num] + inc < 0x00FFFFFF)) { + emulator->data.counter[cnt_num] += inc; + emulator->data_changed = true; + // ACK + buff_tx[0] = 0x0A; + tx_bits = 4; + *data_type = FURI_HAL_NFC_TXRX_RAW; + command_parsed = true; + } } } else if(cmd == MF_UL_AUTH) { - if(emulator->data.type >= MfUltralightTypeNTAG213) { + if(emulator->data.type >= MfUltralightTypeNTAG213 && + emulator->data.type != MfUltralightTypeNTAGI2C1K && + emulator->data.type != MfUltralightTypeNTAGI2C2K) { if(memcmp(&buff_rx[1], emulator->auth_data->pwd, 4) == 0) { buff_tx[0] = emulator->auth_data->pack.raw[0]; buff_tx[1] = emulator->auth_data->pack.raw[1]; tx_bytes = 2; *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + emulator->auth_success = true; command_parsed = true; } else if(!emulator->auth_data->pack.value) { buff_tx[0] = 0x80; @@ -381,36 +1015,81 @@ bool mf_ul_prepare_emulation_response( } } } else if(cmd == MF_UL_READ_SIG) { - // Check 2nd byte = 0x00 - RFU - if(buff_rx[1] == 0x00) { - tx_bytes = sizeof(emulator->data.signature); - memcpy(buff_tx, emulator->data.signature, tx_bytes); - *data_type = FURI_HAL_NFC_TXRX_DEFAULT; - command_parsed = true; + if(emulator->data.type != MfUltralightTypeNTAGI2C1K && + emulator->data.type != MfUltralightTypeNTAGI2C2K) { + // Check 2nd byte = 0x00 - RFU + if(buff_rx[1] == 0x00) { + tx_bytes = sizeof(emulator->data.signature); + memcpy(buff_tx, emulator->data.signature, tx_bytes); + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } } } else if(cmd == MF_UL_CHECK_TEARING) { - uint8_t cnt_num = buff_rx[1]; - if(cnt_num < 3) { - buff_tx[0] = emulator->data.tearing[cnt_num]; - tx_bytes = 1; - *data_type = FURI_HAL_NFC_TXRX_DEFAULT; - command_parsed = true; + if(emulator->data.type < MfUltralightTypeNTAGI2C1K) { + uint8_t cnt_num = buff_rx[1]; + if(cnt_num < 3) { + buff_tx[0] = emulator->data.tearing[cnt_num]; + tx_bytes = 1; + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } } } else if(cmd == MF_UL_HALT_START) { tx_bits = 0; + emulator->curr_sector = 0; + emulator->ntag_i2c_plus_sector3_lockout = false; + emulator->auth_success = false; command_parsed = true; + FURI_LOG_D(TAG, "Received HLTA"); + } else if(cmd == MF_UL_SECTOR_SELECT) { + if(emulator->data.type >= MfUltralightTypeNTAGI2C1K) { + if(buff_rx[1] == 0xFF) { + // Send ACK + emulator->sector_select_cmd_started = true; + send_ack = true; + command_parsed = true; + } + } } if(!command_parsed) { // Send NACK buff_tx[0] = 0x00; tx_bits = 4; - *data_type = FURI_HAL_NFC_TXRX_RAW; + *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT; + } else if(send_ack) { + buff_tx[0] = 0x0A; + tx_bits = 4; + *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT; } - // Return tx buffer size in bits - if(tx_bytes) { - tx_bits = tx_bytes * 8; + + if(respond_nothing) { + *buff_tx_len = UINT16_MAX; + *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT; + } else { + // Return tx buffer size in bits + if(tx_bytes) { + tx_bits = tx_bytes * 8; + } + *buff_tx_len = tx_bits; } - *buff_tx_len = tx_bits; + +#ifdef FURI_DEBUG + if(*buff_tx_len == UINT16_MAX) { + FURI_LOG_T(TAG, "Emu TX: no reply"); + } else if(*buff_tx_len > 0) { + int count = (*buff_tx_len + 7) / 8; + for(int i = 0; i < count; ++i) { + string_cat_printf(debug_buf, "%02x ", buff_tx[i]); + } + string_strim(debug_buf); + FURI_LOG_T(TAG, "Emu TX (%d): %s", *buff_tx_len, string_get_cstr(debug_buf)); + string_clear(debug_buf); + } else { + FURI_LOG_T(TAG, "Emu TX: HALT"); + } +#endif + return tx_bits > 0; } diff --git a/lib/nfc_protocols/mifare_ultralight.h b/lib/nfc_protocols/mifare_ultralight.h index 858fc3ec..76dc2edb 100644 --- a/lib/nfc_protocols/mifare_ultralight.h +++ b/lib/nfc_protocols/mifare_ultralight.h @@ -2,7 +2,8 @@ #include -#define MF_UL_MAX_DUMP_SIZE 1024 +// Largest tag is NTAG I2C Plus 2K, both data sectors plus SRAM +#define MF_UL_MAX_DUMP_SIZE ((238 + 256 + 16) * 4) #define MF_UL_TEARING_FLAG_DEFAULT (0xBD) @@ -11,6 +12,7 @@ #define MF_UL_READ_CMD (0x30) #define MF_UL_FAST_READ_CMD (0x3A) #define MF_UL_WRITE (0xA2) +#define MF_UL_FAST_WRITE (0xA6) #define MF_UL_COMP_WRITE (0xA0) #define MF_UL_READ_CNT (0x39) #define MF_UL_INC_CNT (0xA5) @@ -18,6 +20,7 @@ #define MF_UL_READ_SIG (0x3C) #define MF_UL_CHECK_TEARING (0x3E) #define MF_UL_READ_VCSL (0x4B) +#define MF_UL_SECTOR_SELECT (0xC2) typedef enum { MfUltralightTypeUnknown, @@ -26,6 +29,10 @@ typedef enum { MfUltralightTypeNTAG213, MfUltralightTypeNTAG215, MfUltralightTypeNTAG216, + MfUltralightTypeNTAGI2C1K, + MfUltralightTypeNTAGI2C2K, + MfUltralightTypeNTAGI2CPlus1K, + MfUltralightTypeNTAGI2CPlus2K, // Keep last for number of types calculation MfUltralightTypeNum, @@ -71,11 +78,12 @@ typedef struct { } MfUltralightAuth; typedef struct { - uint8_t pages_to_read; - uint8_t pages_read; + uint16_t pages_to_read; + int16_t pages_read; bool support_fast_read; bool support_tearing_flags; bool support_counters; + bool support_signature; } MfUltralightReader; typedef struct { @@ -85,6 +93,10 @@ typedef struct { bool comp_write_cmd_started; uint8_t comp_write_page_addr; MfUltralightAuth* auth_data; + bool auth_success; + uint8_t curr_sector; + bool sector_select_cmd_started; + bool ntag_i2c_plus_sector3_lockout; } MfUltralightEmulator; bool mf_ul_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); diff --git a/lib/nfc_protocols/nfca.c b/lib/nfc_protocols/nfca.c index 81a6ddfc..e1cbdafe 100755 --- a/lib/nfc_protocols/nfca.c +++ b/lib/nfc_protocols/nfca.c @@ -1,11 +1,17 @@ #include "nfca.h" #include #include +#include #define NFCA_CMD_RATS (0xE0U) #define NFCA_CRC_INIT (0x6363) +#define NFCA_F_SIG (13560000.0) +#define NFCA_T_SIG (1.0 / NFCA_F_SIG) + +#define NFCA_SIGNAL_MAX_EDGES (1350) + typedef struct { uint8_t cmd; uint8_t param; @@ -53,3 +59,81 @@ bool nfca_emulation_handler( return sleep; } + +static void nfca_add_bit(DigitalSignal* signal, bool bit) { + if(bit) { + signal->start_level = true; + for(size_t i = 0; i < 7; i++) { + signal->edge_timings[i] = 8 * NFCA_T_SIG; + } + signal->edge_timings[7] = 9 * 8 * NFCA_T_SIG; + signal->edge_cnt = 8; + } else { + signal->start_level = false; + signal->edge_timings[0] = 8 * 8 * NFCA_T_SIG; + for(size_t i = 1; i < 9; i++) { + signal->edge_timings[i] = 8 * NFCA_T_SIG; + } + signal->edge_cnt = 9; + } +} + +static void nfca_add_byte(NfcaSignal* nfca_signal, uint8_t byte, bool parity) { + for(uint8_t i = 0; i < 8; i++) { + if(byte & (1 << i)) { + digital_signal_append(nfca_signal->tx_signal, nfca_signal->one); + } else { + digital_signal_append(nfca_signal->tx_signal, nfca_signal->zero); + } + } + if(parity) { + digital_signal_append(nfca_signal->tx_signal, nfca_signal->one); + } else { + digital_signal_append(nfca_signal->tx_signal, nfca_signal->zero); + } +} + +NfcaSignal* nfca_signal_alloc() { + NfcaSignal* nfca_signal = malloc(sizeof(NfcaSignal)); + nfca_signal->one = digital_signal_alloc(10); + nfca_signal->zero = digital_signal_alloc(10); + nfca_add_bit(nfca_signal->one, true); + nfca_add_bit(nfca_signal->zero, false); + nfca_signal->tx_signal = digital_signal_alloc(NFCA_SIGNAL_MAX_EDGES); + + return nfca_signal; +} + +void nfca_signal_free(NfcaSignal* nfca_signal) { + furi_assert(nfca_signal); + + digital_signal_free(nfca_signal->one); + digital_signal_free(nfca_signal->zero); + digital_signal_free(nfca_signal->tx_signal); + free(nfca_signal); +} + +void nfca_signal_encode(NfcaSignal* nfca_signal, uint8_t* data, uint16_t bits, uint8_t* parity) { + furi_assert(nfca_signal); + furi_assert(data); + furi_assert(parity); + + nfca_signal->tx_signal->edge_cnt = 0; + nfca_signal->tx_signal->start_level = true; + // Start of frame + digital_signal_append(nfca_signal->tx_signal, nfca_signal->one); + + if(bits < 8) { + for(size_t i = 0; i < bits; i++) { + if(FURI_BIT(data[0], i)) { + digital_signal_append(nfca_signal->tx_signal, nfca_signal->one); + } else { + digital_signal_append(nfca_signal->tx_signal, nfca_signal->zero); + } + } + } else { + for(size_t i = 0; i < bits / 8; i++) { + nfca_add_byte(nfca_signal, data[i], parity[i / 8] & (1 << (7 - (i & 0x07)))); + } + } +} diff --git a/lib/nfc_protocols/nfca.h b/lib/nfc_protocols/nfca.h index 73e2e65e..498ef284 100644 --- a/lib/nfc_protocols/nfca.h +++ b/lib/nfc_protocols/nfca.h @@ -3,6 +3,14 @@ #include #include +#include + +typedef struct { + DigitalSignal* one; + DigitalSignal* zero; + DigitalSignal* tx_signal; +} NfcaSignal; + uint16_t nfca_get_crc16(uint8_t* buff, uint16_t len); void nfca_append_crc16(uint8_t* buff, uint16_t len); @@ -12,3 +20,9 @@ bool nfca_emulation_handler( uint16_t buff_rx_len, uint8_t* buff_tx, uint16_t* buff_tx_len); + +NfcaSignal* nfca_signal_alloc(); + +void nfca_signal_free(NfcaSignal* nfca_signal); + +void nfca_signal_encode(NfcaSignal* nfca_signal, uint8_t* data, uint16_t bits, uint8_t* parity); diff --git a/lib/one_wire/ibutton/ibutton_key.c b/lib/one_wire/ibutton/ibutton_key.c index c6d4466f..2c0f7fa2 100644 --- a/lib/one_wire/ibutton/ibutton_key.c +++ b/lib/one_wire/ibutton/ibutton_key.c @@ -4,7 +4,6 @@ struct iButtonKey { uint8_t data[IBUTTON_KEY_DATA_SIZE]; - char name[IBUTTON_KEY_NAME_SIZE]; iButtonKeyType type; }; @@ -42,14 +41,6 @@ uint8_t ibutton_key_get_data_size(iButtonKey* key) { return ibutton_key_get_size_by_type(key->type); } -void ibutton_key_set_name(iButtonKey* key, const char* name) { - strlcpy(key->name, name, IBUTTON_KEY_NAME_SIZE); -} - -const char* ibutton_key_get_name_p(iButtonKey* key) { - return key->name; -} - void ibutton_key_set_type(iButtonKey* key, iButtonKeyType key_type) { key->type = key_type; } diff --git a/lib/one_wire/ibutton/ibutton_key.h b/lib/one_wire/ibutton/ibutton_key.h index 8d6732bc..f66537d7 100644 --- a/lib/one_wire/ibutton/ibutton_key.h +++ b/lib/one_wire/ibutton/ibutton_key.h @@ -68,20 +68,6 @@ const uint8_t* ibutton_key_get_data_p(iButtonKey* key); */ uint8_t ibutton_key_get_data_size(iButtonKey* key); -/** - * Set key name - * @param key - * @param name - */ -void ibutton_key_set_name(iButtonKey* key, const char* name); - -/** - * Get pointer to key name - * @param key - * @return const char* - */ -const char* ibutton_key_get_name_p(iButtonKey* key); - /** * Set key type * @param key diff --git a/lib/subghz/blocks/encoder.c b/lib/subghz/blocks/encoder.c index e4b1ddba..3cefc2c1 100644 --- a/lib/subghz/blocks/encoder.c +++ b/lib/subghz/blocks/encoder.c @@ -1,3 +1,45 @@ #include "encoder.h" +#include "math.h" +#include #define TAG "SubGhzBlockEncoder" + +void subghz_protocol_blocks_set_bit_array( + bool bit_value, + uint8_t data_array[], + size_t set_index_bit, + size_t max_size_array) { + furi_assert(set_index_bit < max_size_array * 8); + bit_write(data_array[set_index_bit >> 3], 7 - (set_index_bit & 0x7), bit_value); +} + +bool subghz_protocol_blocks_get_bit_array(uint8_t data_array[], size_t read_index_bit) { + return bit_read(data_array[read_index_bit >> 3], 7 - (read_index_bit & 0x7)); +} + +size_t subghz_protocol_blocks_get_upload( + uint8_t data_array[], + size_t count_bit_data_array, + LevelDuration* upload, + size_t max_size_upload, + uint32_t duration_bit) { + size_t index_bit = 0; + size_t size_upload = 0; + uint32_t duration = duration_bit; + bool last_bit = subghz_protocol_blocks_get_bit_array(data_array, index_bit++); + for(size_t i = 1; i < count_bit_data_array; i++) { + if(last_bit == subghz_protocol_blocks_get_bit_array(data_array, index_bit)) { + duration += duration_bit; + } else { + furi_assert(max_size_upload > size_upload); + upload[size_upload++] = level_duration_make( + subghz_protocol_blocks_get_bit_array(data_array, index_bit - 1), duration); + last_bit = !last_bit; + duration = duration_bit; + } + index_bit++; + } + upload[size_upload++] = level_duration_make( + subghz_protocol_blocks_get_bit_array(data_array, index_bit - 1), duration); + return size_upload; +} \ No newline at end of file diff --git a/lib/subghz/blocks/encoder.h b/lib/subghz/blocks/encoder.h index 5c84fcd1..80ffe490 100644 --- a/lib/subghz/blocks/encoder.h +++ b/lib/subghz/blocks/encoder.h @@ -14,3 +14,39 @@ typedef struct { LevelDuration* upload; } SubGhzProtocolBlockEncoder; + +/** + * Set data bit when encoding HEX array. + * @param bit_value The value of the bit to be set + * @param data_array Pointer to a HEX array + * @param set_index_bit Number set a bit in the array starting from the left + * @param max_size_array array size, check not to overflow + */ +void subghz_protocol_blocks_set_bit_array( + bool bit_value, + uint8_t data_array[], + size_t set_index_bit, + size_t max_size_array); + +/** + * Get data bit when encoding HEX array. + * @param data_array Pointer to a HEX array + * @param read_index_bit Number get a bit in the array starting from the left + * @return bool value bit + */ +bool subghz_protocol_blocks_get_bit_array(uint8_t data_array[], size_t read_index_bit); + +/** + * Generating an upload from data. + * @param data_array Pointer to a HEX array + * @param count_bit_data_array How many bits in the array are processed + * @param upload Pointer to a LevelDuration + * @param max_size_upload upload size, check not to overflow + * @param duration_bit duration 1 bit + */ +size_t subghz_protocol_blocks_get_upload( + uint8_t data_array[], + size_t count_bit_data_array, + LevelDuration* upload, + size_t max_size_upload, + uint32_t duration_bit); diff --git a/lib/subghz/protocols/came_atomo.c b/lib/subghz/protocols/came_atomo.c index 51e7fe0b..ad98f83f 100644 --- a/lib/subghz/protocols/came_atomo.c +++ b/lib/subghz/protocols/came_atomo.c @@ -108,8 +108,8 @@ void subghz_protocol_decoder_came_atomo_feed(void* context, bool level, uint32_t ManchesterEvent event = ManchesterEventReset; switch(instance->decoder.parser_step) { case CameAtomoDecoderStepReset: - if((!level) && (DURATION_DIFF(duration, subghz_protocol_came_atomo_const.te_long * 65) < - subghz_protocol_came_atomo_const.te_delta * 20)) { + if((!level) && (DURATION_DIFF(duration, subghz_protocol_came_atomo_const.te_long * 60) < + subghz_protocol_came_atomo_const.te_delta * 40)) { //Found header CAME instance->decoder.parser_step = CameAtomoDecoderStepDecoderData; instance->decoder.decode_data = 0; diff --git a/lib/subghz/protocols/came_twee.c b/lib/subghz/protocols/came_twee.c index 4ca11623..1b60e111 100644 --- a/lib/subghz/protocols/came_twee.c +++ b/lib/subghz/protocols/came_twee.c @@ -145,8 +145,7 @@ static LevelDuration break; default: - FURI_LOG_E(TAG, "DO CRASH HERE."); - furi_crash(NULL); + furi_crash("SubGhz: ManchesterEncoderResult is incorrect."); break; } return level_duration_make(data.level, data.duration); diff --git a/lib/subghz/protocols/chamberlain_code.c b/lib/subghz/protocols/chamberlain_code.c new file mode 100644 index 00000000..23ac8cc8 --- /dev/null +++ b/lib/subghz/protocols/chamberlain_code.c @@ -0,0 +1,483 @@ +#include "chamberlain_code.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolChamb_Code" + +#define CHAMBERLAIN_CODE_BIT_STOP 0b0001 +#define CHAMBERLAIN_CODE_BIT_1 0b0011 +#define CHAMBERLAIN_CODE_BIT_0 0b0111 + +#define CHAMBERLAIN_7_CODE_MASK 0xF000000FF0F +#define CHAMBERLAIN_8_CODE_MASK 0xF00000F00F +#define CHAMBERLAIN_9_CODE_MASK 0xF000000000F + +#define CHAMBERLAIN_7_CODE_MASK_CHECK 0x10000001101 +#define CHAMBERLAIN_8_CODE_MASK_CHECK 0x1000001001 +#define CHAMBERLAIN_9_CODE_MASK_CHECK 0x10000000001 + +#define CHAMBERLAIN_7_CODE_DIP_PATTERN "%c%c%c%c%c%c%c" +#define CHAMBERLAIN_7_CODE_DATA_TO_DIP(dip) \ + (dip & 0x0040 ? '1' : '0'), (dip & 0x0020 ? '1' : '0'), (dip & 0x0010 ? '1' : '0'), \ + (dip & 0x0008 ? '1' : '0'), (dip & 0x0004 ? '1' : '0'), (dip & 0x0002 ? '1' : '0'), \ + (dip & 0x0001 ? '1' : '0') + +#define CHAMBERLAIN_8_CODE_DIP_PATTERN "%c%c%c%c%cx%c%c" +#define CHAMBERLAIN_8_CODE_DATA_TO_DIP(dip) \ + (dip & 0x0080 ? '1' : '0'), (dip & 0x0040 ? '1' : '0'), (dip & 0x0020 ? '1' : '0'), \ + (dip & 0x0010 ? '1' : '0'), (dip & 0x0008 ? '1' : '0'), (dip & 0x0001 ? '1' : '0'), \ + (dip & 0x0002 ? '1' : '0') + +#define CHAMBERLAIN_9_CODE_DIP_PATTERN "%c%c%c%c%c%c%c%c%c" +#define CHAMBERLAIN_9_CODE_DATA_TO_DIP(dip) \ + (dip & 0x0100 ? '1' : '0'), (dip & 0x0080 ? '1' : '0'), (dip & 0x0040 ? '1' : '0'), \ + (dip & 0x0020 ? '1' : '0'), (dip & 0x0010 ? '1' : '0'), (dip & 0x0008 ? '1' : '0'), \ + (dip & 0x0001 ? '1' : '0'), (dip & 0x0002 ? '1' : '0'), (dip & 0x0004 ? '1' : '0') + +static const SubGhzBlockConst subghz_protocol_chamb_code_const = { + .te_short = 1000, + .te_long = 3000, + .te_delta = 200, + .min_count_bit_for_found = 10, +}; + +struct SubGhzProtocolDecoderChamb_Code { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderChamb_Code { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + Chamb_CodeDecoderStepReset = 0, + Chamb_CodeDecoderStepFoundStartBit, + Chamb_CodeDecoderStepSaveDuration, + Chamb_CodeDecoderStepCheckDuration, +} Chamb_CodeDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_chamb_code_decoder = { + .alloc = subghz_protocol_decoder_chamb_code_alloc, + .free = subghz_protocol_decoder_chamb_code_free, + + .feed = subghz_protocol_decoder_chamb_code_feed, + .reset = subghz_protocol_decoder_chamb_code_reset, + + .get_hash_data = subghz_protocol_decoder_chamb_code_get_hash_data, + .serialize = subghz_protocol_decoder_chamb_code_serialize, + .deserialize = subghz_protocol_decoder_chamb_code_deserialize, + .get_string = subghz_protocol_decoder_chamb_code_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_chamb_code_encoder = { + .alloc = subghz_protocol_encoder_chamb_code_alloc, + .free = subghz_protocol_encoder_chamb_code_free, + + .deserialize = subghz_protocol_encoder_chamb_code_deserialize, + .stop = subghz_protocol_encoder_chamb_code_stop, + .yield = subghz_protocol_encoder_chamb_code_yield, +}; + +const SubGhzProtocol subghz_protocol_chamb_code = { + .name = SUBGHZ_PROTOCOL_CHAMB_CODE_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_chamb_code_decoder, + .encoder = &subghz_protocol_chamb_code_encoder, +}; + +void* subghz_protocol_encoder_chamb_code_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderChamb_Code* instance = malloc(sizeof(SubGhzProtocolEncoderChamb_Code)); + + instance->base.protocol = &subghz_protocol_chamb_code; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 24; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_runing = false; + return instance; +} + +void subghz_protocol_encoder_chamb_code_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderChamb_Code* instance = context; + free(instance->encoder.upload); + free(instance); +} + +static uint64_t subghz_protocol_chamb_bit_to_code(uint64_t data, uint8_t size) { + uint64_t data_res = 0; + for(uint8_t i = 0; i < size; i++) { + if(!(bit_read(data, size - i - 1))) { + data_res = data_res << 4 | CHAMBERLAIN_CODE_BIT_0; + } else { + data_res = data_res << 4 | CHAMBERLAIN_CODE_BIT_1; + } + } + return data_res; +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderChamb_Code instance + * @return true On success + */ +static bool + subghz_protocol_encoder_chamb_code_get_upload(SubGhzProtocolEncoderChamb_Code* instance) { + furi_assert(instance); + + uint64_t data = subghz_protocol_chamb_bit_to_code( + instance->generic.data, instance->generic.data_count_bit); + + switch(instance->generic.data_count_bit) { + case 7: + data = ((data >> 4) << 16) | (data & 0xF) << 4 | CHAMBERLAIN_7_CODE_MASK_CHECK; + break; + case 8: + data = ((data >> 12) << 16) | (data & 0xFF) << 4 | CHAMBERLAIN_8_CODE_MASK_CHECK; + break; + case 9: + data = (data << 4) | CHAMBERLAIN_9_CODE_MASK_CHECK; + break; + + default: + furi_crash(TAG " unknown protocol."); + return false; + break; + } +#define UPLOAD_HEX_DATA_SIZE 10 + uint8_t upload_hex_data[UPLOAD_HEX_DATA_SIZE] = {0}; + size_t upload_hex_count_bit = 0; + + //insert guard time + for(uint8_t i = 0; i < 36; i++) { + subghz_protocol_blocks_set_bit_array( + 0, upload_hex_data, upload_hex_count_bit++, UPLOAD_HEX_DATA_SIZE); + } + + //insert data + switch(instance->generic.data_count_bit) { + case 7: + case 9: + for(uint8_t i = 44; i > 0; i--) { + if(!bit_read(data, i - 1)) { + subghz_protocol_blocks_set_bit_array( + 0, upload_hex_data, upload_hex_count_bit++, UPLOAD_HEX_DATA_SIZE); + } else { + subghz_protocol_blocks_set_bit_array( + 1, upload_hex_data, upload_hex_count_bit++, UPLOAD_HEX_DATA_SIZE); + } + } + break; + case 8: + for(uint8_t i = 40; i > 0; i--) { + if(!bit_read(data, i - 1)) { + subghz_protocol_blocks_set_bit_array( + 0, upload_hex_data, upload_hex_count_bit++, UPLOAD_HEX_DATA_SIZE); + } else { + subghz_protocol_blocks_set_bit_array( + 1, upload_hex_data, upload_hex_count_bit++, UPLOAD_HEX_DATA_SIZE); + } + } + break; + } + + instance->encoder.size_upload = subghz_protocol_blocks_get_upload( + upload_hex_data, + upload_hex_count_bit, + instance->encoder.upload, + instance->encoder.size_upload, + subghz_protocol_chamb_code_const.te_short); + + return true; +} + +bool subghz_protocol_encoder_chamb_code_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderChamb_Code* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_encoder_chamb_code_get_upload(instance); + instance->encoder.is_runing = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_chamb_code_stop(void* context) { + SubGhzProtocolEncoderChamb_Code* instance = context; + instance->encoder.is_runing = false; +} + +LevelDuration subghz_protocol_encoder_chamb_code_yield(void* context) { + SubGhzProtocolEncoderChamb_Code* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_runing) { + instance->encoder.is_runing = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_chamb_code_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderChamb_Code* instance = malloc(sizeof(SubGhzProtocolDecoderChamb_Code)); + instance->base.protocol = &subghz_protocol_chamb_code; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_chamb_code_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderChamb_Code* instance = context; + free(instance); +} + +void subghz_protocol_decoder_chamb_code_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderChamb_Code* instance = context; + instance->decoder.parser_step = Chamb_CodeDecoderStepReset; +} + +static bool subghz_protocol_chamb_code_to_bit(uint64_t* data, uint8_t size) { + uint64_t data_tmp = data[0]; + uint64_t data_res = 0; + for(uint8_t i = 0; i < size; i++) { + if((data_tmp & 0xF) == CHAMBERLAIN_CODE_BIT_0) { + bit_write(data_res, i, 0); + } else if((data_tmp & 0xF) == CHAMBERLAIN_CODE_BIT_1) { + bit_write(data_res, i, 1); + } else { + return false; + } + data_tmp >>= 4; + } + data[0] = data_res; + return true; +} + +static bool subghz_protocol_decoder_chamb_code_check_mask_and_parse( + SubGhzProtocolDecoderChamb_Code* instance) { + furi_assert(instance); + if(instance->decoder.decode_count_bit > + subghz_protocol_chamb_code_const.min_count_bit_for_found + 1) + return false; + + if((instance->decoder.decode_data & CHAMBERLAIN_7_CODE_MASK) == + CHAMBERLAIN_7_CODE_MASK_CHECK) { + instance->decoder.decode_count_bit = 7; + instance->decoder.decode_data &= ~CHAMBERLAIN_7_CODE_MASK; + instance->decoder.decode_data = (instance->decoder.decode_data >> 12) | + ((instance->decoder.decode_data >> 4) & 0xF); + } else if( + (instance->decoder.decode_data & CHAMBERLAIN_8_CODE_MASK) == + CHAMBERLAIN_8_CODE_MASK_CHECK) { + instance->decoder.decode_count_bit = 8; + instance->decoder.decode_data &= ~CHAMBERLAIN_8_CODE_MASK; + instance->decoder.decode_data = instance->decoder.decode_data >> 4 | + CHAMBERLAIN_CODE_BIT_0 << 8; //DIP 6 no use + } else if( + (instance->decoder.decode_data & CHAMBERLAIN_9_CODE_MASK) == + CHAMBERLAIN_9_CODE_MASK_CHECK) { + instance->decoder.decode_count_bit = 9; + instance->decoder.decode_data &= ~CHAMBERLAIN_9_CODE_MASK; + instance->decoder.decode_data >>= 4; + } else { + return false; + } + return subghz_protocol_chamb_code_to_bit( + &instance->decoder.decode_data, instance->decoder.decode_count_bit); +} + +void subghz_protocol_decoder_chamb_code_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderChamb_Code* instance = context; + switch(instance->decoder.parser_step) { + case Chamb_CodeDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_chamb_code_const.te_short * 39) < + subghz_protocol_chamb_code_const.te_delta * 20)) { + //Found header Chamb_Code + instance->decoder.parser_step = Chamb_CodeDecoderStepFoundStartBit; + } + break; + case Chamb_CodeDecoderStepFoundStartBit: + if((level) && (DURATION_DIFF(duration, subghz_protocol_chamb_code_const.te_short) < + subghz_protocol_chamb_code_const.te_delta)) { + //Found start bit Chamb_Code + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.decode_data = instance->decoder.decode_data << 4 | + CHAMBERLAIN_CODE_BIT_STOP; + instance->decoder.decode_count_bit++; + instance->decoder.parser_step = Chamb_CodeDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Chamb_CodeDecoderStepReset; + } + break; + case Chamb_CodeDecoderStepSaveDuration: + if(!level) { //save interval + if(duration > subghz_protocol_chamb_code_const.te_short * 5) { + if(instance->decoder.decode_count_bit >= + subghz_protocol_chamb_code_const.min_count_bit_for_found) { + instance->generic.serial = 0x0; + instance->generic.btn = 0x0; + if(subghz_protocol_decoder_chamb_code_check_mask_and_parse(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + } + instance->decoder.parser_step = Chamb_CodeDecoderStepReset; + } else { + instance->decoder.te_last = duration; + instance->decoder.parser_step = Chamb_CodeDecoderStepCheckDuration; + } + } else { + instance->decoder.parser_step = Chamb_CodeDecoderStepReset; + } + break; + case Chamb_CodeDecoderStepCheckDuration: + if(level) { + if((DURATION_DIFF( //Found stop bit Chamb_Code + instance->decoder.te_last, + subghz_protocol_chamb_code_const.te_short * 3) < + subghz_protocol_chamb_code_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_chamb_code_const.te_short) < + subghz_protocol_chamb_code_const.te_delta)) { + instance->decoder.decode_data = instance->decoder.decode_data << 4 | + CHAMBERLAIN_CODE_BIT_STOP; + instance->decoder.decode_count_bit++; + instance->decoder.parser_step = Chamb_CodeDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_chamb_code_const.te_short * 2) < + subghz_protocol_chamb_code_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_chamb_code_const.te_short * 2) < + subghz_protocol_chamb_code_const.te_delta)) { + instance->decoder.decode_data = instance->decoder.decode_data << 4 | + CHAMBERLAIN_CODE_BIT_1; + instance->decoder.decode_count_bit++; + instance->decoder.parser_step = Chamb_CodeDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_chamb_code_const.te_short) < + subghz_protocol_chamb_code_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_chamb_code_const.te_short * 3) < + subghz_protocol_chamb_code_const.te_delta)) { + instance->decoder.decode_data = instance->decoder.decode_data << 4 | + CHAMBERLAIN_CODE_BIT_0; + instance->decoder.decode_count_bit++; + instance->decoder.parser_step = Chamb_CodeDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Chamb_CodeDecoderStepReset; + } + + } else { + instance->decoder.parser_step = Chamb_CodeDecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_chamb_code_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderChamb_Code* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_chamb_code_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset) { + furi_assert(context); + SubGhzProtocolDecoderChamb_Code* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, frequency, preset); +} + +bool subghz_protocol_decoder_chamb_code_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderChamb_Code* instance = context; + return subghz_block_generic_deserialize(&instance->generic, flipper_format); +} + +void subghz_protocol_decoder_chamb_code_get_string(void* context, string_t output) { + furi_assert(context); + SubGhzProtocolDecoderChamb_Code* instance = context; + + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + + uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; + + string_cat_printf( + output, + "%s %db\r\n" + "Key:0x%03lX\r\n" + "Yek:0x%03lX\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_lo, + code_found_reverse_lo); + + switch(instance->generic.data_count_bit) { + case 7: + string_cat_printf( + output, + "DIP:" CHAMBERLAIN_7_CODE_DIP_PATTERN "\r\n", + CHAMBERLAIN_7_CODE_DATA_TO_DIP(code_found_lo)); + break; + case 8: + string_cat_printf( + output, + "DIP:" CHAMBERLAIN_8_CODE_DIP_PATTERN "\r\n", + CHAMBERLAIN_8_CODE_DATA_TO_DIP(code_found_lo)); + break; + case 9: + string_cat_printf( + output, + "DIP:" CHAMBERLAIN_9_CODE_DIP_PATTERN "\r\n", + CHAMBERLAIN_9_CODE_DATA_TO_DIP(code_found_lo)); + break; + + default: + break; + } +} diff --git a/lib/subghz/protocols/chamberlain_code.h b/lib/subghz/protocols/chamberlain_code.h new file mode 100644 index 00000000..f6ef48fd --- /dev/null +++ b/lib/subghz/protocols/chamberlain_code.h @@ -0,0 +1,109 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_CHAMB_CODE_NAME "Cham_Code" + +typedef struct SubGhzProtocolDecoderChamb_Code SubGhzProtocolDecoderChamb_Code; +typedef struct SubGhzProtocolEncoderChamb_Code SubGhzProtocolEncoderChamb_Code; + +extern const SubGhzProtocolDecoder subghz_protocol_chamb_code_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_chamb_code_encoder; +extern const SubGhzProtocol subghz_protocol_chamb_code; + +/** + * Allocate SubGhzProtocolEncoderChamb_Code. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderChamb_Code* pointer to a SubGhzProtocolEncoderChamb_Code instance + */ +void* subghz_protocol_encoder_chamb_code_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderChamb_Code. + * @param context Pointer to a SubGhzProtocolEncoderChamb_Code instance + */ +void subghz_protocol_encoder_chamb_code_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderChamb_Code instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_chamb_code_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderChamb_Code instance + */ +void subghz_protocol_encoder_chamb_code_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderChamb_Code instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_chamb_code_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderChamb_Code. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderChamb_Code* pointer to a SubGhzProtocolDecoderChamb_Code instance + */ +void* subghz_protocol_decoder_chamb_code_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderChamb_Code. + * @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance + */ +void subghz_protocol_decoder_chamb_code_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderChamb_Code. + * @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance + */ +void subghz_protocol_decoder_chamb_code_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_chamb_code_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_chamb_code_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderChamb_Code. + * @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param frequency The frequency at which the signal was received, Hz + * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset + * @return true On success + */ +bool subghz_protocol_decoder_chamb_code_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset); + +/** + * Deserialize data SubGhzProtocolDecoderChamb_Code. + * @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_chamb_code_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance + * @param output Resulting text + */ +void subghz_protocol_decoder_chamb_code_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/firefly.h b/lib/subghz/protocols/firefly.h deleted file mode 100644 index abb4537c..00000000 --- a/lib/subghz/protocols/firefly.h +++ /dev/null @@ -1,109 +0,0 @@ -#pragma once - -#include "base.h" - -#define SUBGHZ_PROTOCOL_FIREFLY_NAME "Firefly" - -typedef struct SubGhzProtocolDecoderFirefly SubGhzProtocolDecoderFirefly; -typedef struct SubGhzProtocolEncoderFirefly SubGhzProtocolEncoderFirefly; - -extern const SubGhzProtocolDecoder subghz_protocol_firefly_decoder; -extern const SubGhzProtocolEncoder subghz_protocol_firefly_encoder; -extern const SubGhzProtocol subghz_protocol_firefly; - -/** - * Allocate SubGhzProtocolEncoderFirefly. - * @param environment Pointer to a SubGhzEnvironment instance - * @return SubGhzProtocolEncoderFirefly* pointer to a SubGhzProtocolEncoderFirefly instance - */ -void* subghz_protocol_encoder_firefly_alloc(SubGhzEnvironment* environment); - -/** - * Free SubGhzProtocolEncoderFirefly. - * @param context Pointer to a SubGhzProtocolEncoderFirefly instance - */ -void subghz_protocol_encoder_firefly_free(void* context); - -/** - * Deserialize and generating an upload to send. - * @param context Pointer to a SubGhzProtocolEncoderFirefly instance - * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success - */ -bool subghz_protocol_encoder_firefly_deserialize(void* context, FlipperFormat* flipper_format); - -/** - * Forced transmission stop. - * @param context Pointer to a SubGhzProtocolEncoderFirefly instance - */ -void subghz_protocol_encoder_firefly_stop(void* context); - -/** - * Getting the level and duration of the upload to be loaded into DMA. - * @param context Pointer to a SubGhzProtocolEncoderFirefly instance - * @return LevelDuration - */ -LevelDuration subghz_protocol_encoder_firefly_yield(void* context); - -/** - * Allocate SubGhzProtocolDecoderFirefly. - * @param environment Pointer to a SubGhzEnvironment instance - * @return SubGhzProtocolDecoderFirefly* pointer to a SubGhzProtocolDecoderFirefly instance - */ -void* subghz_protocol_decoder_firefly_alloc(SubGhzEnvironment* environment); - -/** - * Free SubGhzProtocolDecoderFirefly. - * @param context Pointer to a SubGhzProtocolDecoderFirefly instance - */ -void subghz_protocol_decoder_firefly_free(void* context); - -/** - * Reset decoder SubGhzProtocolDecoderFirefly. - * @param context Pointer to a SubGhzProtocolDecoderFirefly instance - */ -void subghz_protocol_decoder_firefly_reset(void* context); - -/** - * Parse a raw sequence of levels and durations received from the air. - * @param context Pointer to a SubGhzProtocolDecoderFirefly instance - * @param level Signal level true-high false-low - * @param duration Duration of this level in, us - */ -void subghz_protocol_decoder_firefly_feed(void* context, bool level, uint32_t duration); - -/** - * Getting the hash sum of the last randomly received parcel. - * @param context Pointer to a SubGhzProtocolDecoderFirefly instance - * @return hash Hash sum - */ -uint8_t subghz_protocol_decoder_firefly_get_hash_data(void* context); - -/** - * Serialize data SubGhzProtocolDecoderFirefly. - * @param context Pointer to a SubGhzProtocolDecoderFirefly instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param frequency The frequency at which the signal was received, Hz - * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset - * @return true On success - */ -bool subghz_protocol_decoder_firefly_serialize( - void* context, - FlipperFormat* flipper_format, - uint32_t frequency, - FuriHalSubGhzPreset preset); - -/** - * Deserialize data SubGhzProtocolDecoderFirefly. - * @param context Pointer to a SubGhzProtocolDecoderFirefly instance - * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success - */ -bool subghz_protocol_decoder_firefly_deserialize(void* context, FlipperFormat* flipper_format); - -/** - * Getting a textual representation of the received data. - * @param context Pointer to a SubGhzProtocolDecoderFirefly instance - * @param output Resulting text - */ -void subghz_protocol_decoder_firefly_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/firefly.c b/lib/subghz/protocols/linear.c similarity index 55% rename from lib/subghz/protocols/firefly.c rename to lib/subghz/protocols/linear.c index 21047a30..6b013278 100644 --- a/lib/subghz/protocols/firefly.c +++ b/lib/subghz/protocols/linear.c @@ -1,4 +1,4 @@ -#include "firefly.h" +#include "linear.h" #include "../blocks/const.h" #include "../blocks/decoder.h" @@ -6,7 +6,7 @@ #include "../blocks/generic.h" #include "../blocks/math.h" -#define TAG "SubGhzProtocolFirefly" +#define TAG "SubGhzProtocolLinear" #define DIP_PATTERN "%c%c%c%c%c%c%c%c%c%c" #define DATA_TO_DIP(dip) \ @@ -15,21 +15,21 @@ (dip & 0x0008 ? '1' : '0'), (dip & 0x0004 ? '1' : '0'), (dip & 0x0002 ? '1' : '0'), \ (dip & 0x0001 ? '1' : '0') -static const SubGhzBlockConst subghz_protocol_firefly_const = { +static const SubGhzBlockConst subghz_protocol_linear_const = { .te_short = 500, .te_long = 1500, .te_delta = 150, .min_count_bit_for_found = 10, }; -struct SubGhzProtocolDecoderFirefly { +struct SubGhzProtocolDecoderLinear { SubGhzProtocolDecoderBase base; SubGhzBlockDecoder decoder; SubGhzBlockGeneric generic; }; -struct SubGhzProtocolEncoderFirefly { +struct SubGhzProtocolEncoderLinear { SubGhzProtocolEncoderBase base; SubGhzProtocolBlockEncoder encoder; @@ -37,48 +37,48 @@ struct SubGhzProtocolEncoderFirefly { }; typedef enum { - FireflyDecoderStepReset = 0, - FireflyDecoderStepSaveDuration, - FireflyDecoderStepCheckDuration, -} FireflyDecoderStep; + LinearDecoderStepReset = 0, + LinearDecoderStepSaveDuration, + LinearDecoderStepCheckDuration, +} LinearDecoderStep; -const SubGhzProtocolDecoder subghz_protocol_firefly_decoder = { - .alloc = subghz_protocol_decoder_firefly_alloc, - .free = subghz_protocol_decoder_firefly_free, +const SubGhzProtocolDecoder subghz_protocol_linear_decoder = { + .alloc = subghz_protocol_decoder_linear_alloc, + .free = subghz_protocol_decoder_linear_free, - .feed = subghz_protocol_decoder_firefly_feed, - .reset = subghz_protocol_decoder_firefly_reset, + .feed = subghz_protocol_decoder_linear_feed, + .reset = subghz_protocol_decoder_linear_reset, - .get_hash_data = subghz_protocol_decoder_firefly_get_hash_data, - .serialize = subghz_protocol_decoder_firefly_serialize, - .deserialize = subghz_protocol_decoder_firefly_deserialize, - .get_string = subghz_protocol_decoder_firefly_get_string, + .get_hash_data = subghz_protocol_decoder_linear_get_hash_data, + .serialize = subghz_protocol_decoder_linear_serialize, + .deserialize = subghz_protocol_decoder_linear_deserialize, + .get_string = subghz_protocol_decoder_linear_get_string, }; -const SubGhzProtocolEncoder subghz_protocol_firefly_encoder = { - .alloc = subghz_protocol_encoder_firefly_alloc, - .free = subghz_protocol_encoder_firefly_free, +const SubGhzProtocolEncoder subghz_protocol_linear_encoder = { + .alloc = subghz_protocol_encoder_linear_alloc, + .free = subghz_protocol_encoder_linear_free, - .deserialize = subghz_protocol_encoder_firefly_deserialize, - .stop = subghz_protocol_encoder_firefly_stop, - .yield = subghz_protocol_encoder_firefly_yield, + .deserialize = subghz_protocol_encoder_linear_deserialize, + .stop = subghz_protocol_encoder_linear_stop, + .yield = subghz_protocol_encoder_linear_yield, }; -const SubGhzProtocol subghz_protocol_firefly = { - .name = SUBGHZ_PROTOCOL_FIREFLY_NAME, +const SubGhzProtocol subghz_protocol_linear = { + .name = SUBGHZ_PROTOCOL_LINEAR_NAME, .type = SubGhzProtocolTypeStatic, .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, - .decoder = &subghz_protocol_firefly_decoder, - .encoder = &subghz_protocol_firefly_encoder, + .decoder = &subghz_protocol_linear_decoder, + .encoder = &subghz_protocol_linear_encoder, }; -void* subghz_protocol_encoder_firefly_alloc(SubGhzEnvironment* environment) { +void* subghz_protocol_encoder_linear_alloc(SubGhzEnvironment* environment) { UNUSED(environment); - SubGhzProtocolEncoderFirefly* instance = malloc(sizeof(SubGhzProtocolEncoderFirefly)); + SubGhzProtocolEncoderLinear* instance = malloc(sizeof(SubGhzProtocolEncoderLinear)); - instance->base.protocol = &subghz_protocol_firefly; + instance->base.protocol = &subghz_protocol_linear; instance->generic.protocol_name = instance->base.protocol->name; instance->encoder.repeat = 10; @@ -88,19 +88,19 @@ void* subghz_protocol_encoder_firefly_alloc(SubGhzEnvironment* environment) { return instance; } -void subghz_protocol_encoder_firefly_free(void* context) { +void subghz_protocol_encoder_linear_free(void* context) { furi_assert(context); - SubGhzProtocolEncoderFirefly* instance = context; + SubGhzProtocolEncoderLinear* instance = context; free(instance->encoder.upload); free(instance); } /** * Generating an upload from data. - * @param instance Pointer to a SubGhzProtocolEncoderFirefly instance + * @param instance Pointer to a SubGhzProtocolEncoderLinear instance * @return true On success */ -static bool subghz_protocol_encoder_firefly_get_upload(SubGhzProtocolEncoderFirefly* instance) { +static bool subghz_protocol_encoder_linear_get_upload(SubGhzProtocolEncoderLinear* instance) { furi_assert(instance); size_t index = 0; size_t size_upload = (instance->generic.data_count_bit * 2); @@ -116,40 +116,40 @@ static bool subghz_protocol_encoder_firefly_get_upload(SubGhzProtocolEncoderFire if(bit_read(instance->generic.data, i - 1)) { //send bit 1 instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)subghz_protocol_firefly_const.te_short * 3); + level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_short * 3); instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)subghz_protocol_firefly_const.te_short); + level_duration_make(false, (uint32_t)subghz_protocol_linear_const.te_short); } else { //send bit 0 instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)subghz_protocol_firefly_const.te_short); + level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_short); instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)subghz_protocol_firefly_const.te_short * 3); + level_duration_make(false, (uint32_t)subghz_protocol_linear_const.te_short * 3); } } //Send end bit if(bit_read(instance->generic.data, 0)) { //send bit 1 instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)subghz_protocol_firefly_const.te_short * 3); + level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_short * 3); //Send PT_GUARD instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)subghz_protocol_firefly_const.te_short * 42); + level_duration_make(false, (uint32_t)subghz_protocol_linear_const.te_short * 42); } else { //send bit 0 instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)subghz_protocol_firefly_const.te_short); + level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_short); //Send PT_GUARD instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)subghz_protocol_firefly_const.te_short * 44); + level_duration_make(false, (uint32_t)subghz_protocol_linear_const.te_short * 44); } return true; } -bool subghz_protocol_encoder_firefly_deserialize(void* context, FlipperFormat* flipper_format) { +bool subghz_protocol_encoder_linear_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); - SubGhzProtocolEncoderFirefly* instance = context; + SubGhzProtocolEncoderLinear* instance = context; bool res = false; do { if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { @@ -161,7 +161,7 @@ bool subghz_protocol_encoder_firefly_deserialize(void* context, FlipperFormat* f flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - subghz_protocol_encoder_firefly_get_upload(instance); + subghz_protocol_encoder_linear_get_upload(instance); instance->encoder.is_runing = true; res = true; @@ -170,13 +170,13 @@ bool subghz_protocol_encoder_firefly_deserialize(void* context, FlipperFormat* f return res; } -void subghz_protocol_encoder_firefly_stop(void* context) { - SubGhzProtocolEncoderFirefly* instance = context; +void subghz_protocol_encoder_linear_stop(void* context) { + SubGhzProtocolEncoderLinear* instance = context; instance->encoder.is_runing = false; } -LevelDuration subghz_protocol_encoder_firefly_yield(void* context) { - SubGhzProtocolEncoderFirefly* instance = context; +LevelDuration subghz_protocol_encoder_linear_yield(void* context) { + SubGhzProtocolEncoderLinear* instance = context; if(instance->encoder.repeat == 0 || !instance->encoder.is_runing) { instance->encoder.is_runing = false; @@ -193,68 +193,66 @@ LevelDuration subghz_protocol_encoder_firefly_yield(void* context) { return ret; } -void* subghz_protocol_decoder_firefly_alloc(SubGhzEnvironment* environment) { +void* subghz_protocol_decoder_linear_alloc(SubGhzEnvironment* environment) { UNUSED(environment); - SubGhzProtocolDecoderFirefly* instance = malloc(sizeof(SubGhzProtocolDecoderFirefly)); - instance->base.protocol = &subghz_protocol_firefly; + SubGhzProtocolDecoderLinear* instance = malloc(sizeof(SubGhzProtocolDecoderLinear)); + instance->base.protocol = &subghz_protocol_linear; instance->generic.protocol_name = instance->base.protocol->name; return instance; } -void subghz_protocol_decoder_firefly_free(void* context) { +void subghz_protocol_decoder_linear_free(void* context) { furi_assert(context); - SubGhzProtocolDecoderFirefly* instance = context; + SubGhzProtocolDecoderLinear* instance = context; free(instance); } -void subghz_protocol_decoder_firefly_reset(void* context) { +void subghz_protocol_decoder_linear_reset(void* context) { furi_assert(context); - SubGhzProtocolDecoderFirefly* instance = context; - instance->decoder.parser_step = FireflyDecoderStepReset; + SubGhzProtocolDecoderLinear* instance = context; + instance->decoder.parser_step = LinearDecoderStepReset; } -void subghz_protocol_decoder_firefly_feed(void* context, bool level, uint32_t duration) { +void subghz_protocol_decoder_linear_feed(void* context, bool level, uint32_t duration) { furi_assert(context); - SubGhzProtocolDecoderFirefly* instance = context; + SubGhzProtocolDecoderLinear* instance = context; switch(instance->decoder.parser_step) { - case FireflyDecoderStepReset: - if((!level) && (DURATION_DIFF(duration, subghz_protocol_firefly_const.te_short * 42) < - subghz_protocol_firefly_const.te_delta * 20)) { - //Found header Firefly + case LinearDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_linear_const.te_short * 42) < + subghz_protocol_linear_const.te_delta * 20)) { + //Found header Linear instance->decoder.decode_data = 0; instance->decoder.decode_count_bit = 0; - instance->decoder.parser_step = FireflyDecoderStepSaveDuration; + instance->decoder.parser_step = LinearDecoderStepSaveDuration; } break; - case FireflyDecoderStepSaveDuration: + case LinearDecoderStepSaveDuration: if(level) { instance->decoder.te_last = duration; - instance->decoder.parser_step = FireflyDecoderStepCheckDuration; + instance->decoder.parser_step = LinearDecoderStepCheckDuration; } else { - instance->decoder.parser_step = FireflyDecoderStepReset; + instance->decoder.parser_step = LinearDecoderStepReset; } break; - case FireflyDecoderStepCheckDuration: + case LinearDecoderStepCheckDuration: if(!level) { //save interval - if(duration >= (subghz_protocol_firefly_const.te_short * 5)) { - instance->decoder.parser_step = FireflyDecoderStepReset; + if(duration >= (subghz_protocol_linear_const.te_short * 5)) { + instance->decoder.parser_step = LinearDecoderStepReset; //checking that the duration matches the guardtime - if((DURATION_DIFF(duration, subghz_protocol_firefly_const.te_short * 42) > - subghz_protocol_firefly_const.te_delta * 20)) { + if((DURATION_DIFF(duration, subghz_protocol_linear_const.te_short * 42) > + subghz_protocol_linear_const.te_delta * 20)) { break; } - if(DURATION_DIFF( - instance->decoder.te_last, subghz_protocol_firefly_const.te_short) < - subghz_protocol_firefly_const.te_delta) { + if(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_linear_const.te_short) < + subghz_protocol_linear_const.te_delta) { subghz_protocol_blocks_add_bit(&instance->decoder, 0); } else if( - DURATION_DIFF( - instance->decoder.te_last, subghz_protocol_firefly_const.te_long) < - subghz_protocol_firefly_const.te_delta) { + DURATION_DIFF(instance->decoder.te_last, subghz_protocol_linear_const.te_long) < + subghz_protocol_linear_const.te_delta) { subghz_protocol_blocks_add_bit(&instance->decoder, 1); } if(instance->decoder.decode_count_bit == - subghz_protocol_firefly_const.min_count_bit_for_found) { + subghz_protocol_linear_const.min_count_bit_for_found) { instance->generic.serial = 0x0; instance->generic.btn = 0x0; @@ -267,56 +265,56 @@ void subghz_protocol_decoder_firefly_feed(void* context, bool level, uint32_t du break; } - if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_firefly_const.te_short) < - subghz_protocol_firefly_const.te_delta) && - (DURATION_DIFF(duration, subghz_protocol_firefly_const.te_long) < - subghz_protocol_firefly_const.te_delta)) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_linear_const.te_short) < + subghz_protocol_linear_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_linear_const.te_long) < + subghz_protocol_linear_const.te_delta)) { subghz_protocol_blocks_add_bit(&instance->decoder, 0); - instance->decoder.parser_step = FireflyDecoderStepSaveDuration; + instance->decoder.parser_step = LinearDecoderStepSaveDuration; } else if( - (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_firefly_const.te_long) < - subghz_protocol_firefly_const.te_delta) && - (DURATION_DIFF(duration, subghz_protocol_firefly_const.te_short) < - subghz_protocol_firefly_const.te_delta)) { + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_linear_const.te_long) < + subghz_protocol_linear_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_linear_const.te_short) < + subghz_protocol_linear_const.te_delta)) { subghz_protocol_blocks_add_bit(&instance->decoder, 1); - instance->decoder.parser_step = FireflyDecoderStepSaveDuration; + instance->decoder.parser_step = LinearDecoderStepSaveDuration; } else { - instance->decoder.parser_step = FireflyDecoderStepReset; + instance->decoder.parser_step = LinearDecoderStepReset; } } else { - instance->decoder.parser_step = FireflyDecoderStepReset; + instance->decoder.parser_step = LinearDecoderStepReset; } break; } } -uint8_t subghz_protocol_decoder_firefly_get_hash_data(void* context) { +uint8_t subghz_protocol_decoder_linear_get_hash_data(void* context) { furi_assert(context); - SubGhzProtocolDecoderFirefly* instance = context; + SubGhzProtocolDecoderLinear* instance = context; return subghz_protocol_blocks_get_hash_data( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_firefly_serialize( +bool subghz_protocol_decoder_linear_serialize( void* context, FlipperFormat* flipper_format, uint32_t frequency, FuriHalSubGhzPreset preset) { furi_assert(context); - SubGhzProtocolDecoderFirefly* instance = context; + SubGhzProtocolDecoderLinear* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, frequency, preset); } -bool subghz_protocol_decoder_firefly_deserialize(void* context, FlipperFormat* flipper_format) { +bool subghz_protocol_decoder_linear_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); - SubGhzProtocolDecoderFirefly* instance = context; + SubGhzProtocolDecoderLinear* instance = context; return subghz_block_generic_deserialize(&instance->generic, flipper_format); } -void subghz_protocol_decoder_firefly_get_string(void* context, string_t output) { +void subghz_protocol_decoder_linear_get_string(void* context, string_t output) { furi_assert(context); - SubGhzProtocolDecoderFirefly* instance = context; + SubGhzProtocolDecoderLinear* instance = context; uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; diff --git a/lib/subghz/protocols/linear.h b/lib/subghz/protocols/linear.h new file mode 100644 index 00000000..44f4e7dd --- /dev/null +++ b/lib/subghz/protocols/linear.h @@ -0,0 +1,109 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_LINEAR_NAME "Linear" + +typedef struct SubGhzProtocolDecoderLinear SubGhzProtocolDecoderLinear; +typedef struct SubGhzProtocolEncoderLinear SubGhzProtocolEncoderLinear; + +extern const SubGhzProtocolDecoder subghz_protocol_linear_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_linear_encoder; +extern const SubGhzProtocol subghz_protocol_linear; + +/** + * Allocate SubGhzProtocolEncoderLinear. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderLinear* pointer to a SubGhzProtocolEncoderLinear instance + */ +void* subghz_protocol_encoder_linear_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderLinear. + * @param context Pointer to a SubGhzProtocolEncoderLinear instance + */ +void subghz_protocol_encoder_linear_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderLinear instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_linear_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderLinear instance + */ +void subghz_protocol_encoder_linear_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderLinear instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_linear_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderLinear. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderLinear* pointer to a SubGhzProtocolDecoderLinear instance + */ +void* subghz_protocol_decoder_linear_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderLinear. + * @param context Pointer to a SubGhzProtocolDecoderLinear instance + */ +void subghz_protocol_decoder_linear_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderLinear. + * @param context Pointer to a SubGhzProtocolDecoderLinear instance + */ +void subghz_protocol_decoder_linear_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderLinear instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_linear_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderLinear instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_linear_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderLinear. + * @param context Pointer to a SubGhzProtocolDecoderLinear instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param frequency The frequency at which the signal was received, Hz + * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset + * @return true On success + */ +bool subghz_protocol_decoder_linear_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset); + +/** + * Deserialize data SubGhzProtocolDecoderLinear. + * @param context Pointer to a SubGhzProtocolDecoderLinear instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_linear_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderLinear instance + * @param output Resulting text + */ +void subghz_protocol_decoder_linear_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/megacode.c b/lib/subghz/protocols/megacode.c index 48a6908e..f8d42b29 100644 --- a/lib/subghz/protocols/megacode.c +++ b/lib/subghz/protocols/megacode.c @@ -247,7 +247,7 @@ void subghz_protocol_decoder_megacode_feed(void* context, bool level, uint32_t d switch(instance->decoder.parser_step) { case MegaCodeDecoderStepReset: if((!level) && (DURATION_DIFF(duration, subghz_protocol_megacode_const.te_short * 13) < - subghz_protocol_megacode_const.te_delta * 15)) { //10..16ms + subghz_protocol_megacode_const.te_delta * 17)) { //10..16ms //Found header MegaCode instance->decoder.parser_step = MegaCodeDecoderStepFoundStartBit; } @@ -401,13 +401,14 @@ void subghz_protocol_decoder_megacode_get_string(void* context, string_t output) string_cat_printf( output, "%s %dbit\r\n" - "Key:%06lX\r\n" - "Sn:%04lX Btn:%X\r\n" - "Facility:%X\r\n", + "Key:0x%06lX\r\n" + "Sn:0x%04lX - %d\r\n" + "Facility:%X Btn:%X\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)instance->generic.data, instance->generic.serial, - instance->generic.btn, - instance->generic.cnt); + instance->generic.serial, + instance->generic.cnt, + instance->generic.btn); } diff --git a/lib/subghz/protocols/registry.c b/lib/subghz/protocols/registry.c index b90e535c..668e6ad5 100644 --- a/lib/subghz/protocols/registry.c +++ b/lib/subghz/protocols/registry.c @@ -7,8 +7,9 @@ const SubGhzProtocol* subghz_protocol_registry[] = { &subghz_protocol_nero_sketch, &subghz_protocol_ido, &subghz_protocol_kia, &subghz_protocol_hormann, &subghz_protocol_nero_radio, &subghz_protocol_somfy_telis, &subghz_protocol_somfy_keytis, &subghz_protocol_scher_khan, &subghz_protocol_princeton, - &subghz_protocol_raw, &subghz_protocol_firefly, &subghz_protocol_secplus_v2, + &subghz_protocol_raw, &subghz_protocol_linear, &subghz_protocol_secplus_v2, &subghz_protocol_secplus_v1, &subghz_protocol_megacode, &subghz_protocol_holtek, + &subghz_protocol_chamb_code, }; diff --git a/lib/subghz/protocols/registry.h b/lib/subghz/protocols/registry.h index a8bc9af4..39322380 100644 --- a/lib/subghz/protocols/registry.h +++ b/lib/subghz/protocols/registry.h @@ -21,11 +21,12 @@ #include "scher_khan.h" #include "gate_tx.h" #include "raw.h" -#include "firefly.h" +#include "linear.h" #include "secplus_v2.h" #include "secplus_v1.h" #include "megacode.h" #include "holtek.h" +#include "chamberlain_code.h" /** * Registration by name SubGhzProtocol. diff --git a/lib/subghz/protocols/secplus_v1.c b/lib/subghz/protocols/secplus_v1.c index ff43e2a4..b51179f4 100644 --- a/lib/subghz/protocols/secplus_v1.c +++ b/lib/subghz/protocols/secplus_v1.c @@ -48,6 +48,8 @@ struct SubGhzProtocolEncoderSecPlus_v1 { SubGhzProtocolBlockEncoder encoder; SubGhzBlockGeneric generic; + + uint8_t data_array[44]; }; typedef enum { @@ -71,23 +73,258 @@ const SubGhzProtocolDecoder subghz_protocol_secplus_v1_decoder = { }; const SubGhzProtocolEncoder subghz_protocol_secplus_v1_encoder = { - .alloc = NULL, - .free = NULL, + .alloc = subghz_protocol_encoder_secplus_v1_alloc, + .free = subghz_protocol_encoder_secplus_v1_free, - .deserialize = NULL, - .stop = NULL, - .yield = NULL, + .deserialize = subghz_protocol_encoder_secplus_v1_deserialize, + .stop = subghz_protocol_encoder_secplus_v1_stop, + .yield = subghz_protocol_encoder_secplus_v1_yield, }; const SubGhzProtocol subghz_protocol_secplus_v1 = { .name = SUBGHZ_PROTOCOL_SECPLUS_V1_NAME, .type = SubGhzProtocolTypeDynamic, - .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Send, .decoder = &subghz_protocol_secplus_v1_decoder, .encoder = &subghz_protocol_secplus_v1_encoder, }; +void* subghz_protocol_encoder_secplus_v1_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderSecPlus_v1* instance = malloc(sizeof(SubGhzProtocolEncoderSecPlus_v1)); + + instance->base.protocol = &subghz_protocol_secplus_v1; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 128; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_runing = false; + return instance; +} + +void subghz_protocol_encoder_secplus_v1_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderSecPlus_v1* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderSecPlus_v1 instance + * @return true On success + */ +static bool + subghz_protocol_encoder_secplus_v1_get_upload(SubGhzProtocolEncoderSecPlus_v1* instance) { + furi_assert(instance); + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2); + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Encoder size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + //Send header packet 1 + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_secplus_v1_const.te_short * (116 + 3)); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_secplus_v1_const.te_short); + + //Send data packet 1 + for(uint8_t i = SECPLUS_V1_PACKET_1_INDEX_BASE + 1; i < SECPLUS_V1_PACKET_1_INDEX_BASE + 21; + i++) { + switch(instance->data_array[i]) { + case SECPLUS_V1_BIT_0: + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_secplus_v1_const.te_short * 3); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_secplus_v1_const.te_short); + break; + case SECPLUS_V1_BIT_1: + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_secplus_v1_const.te_short * 2); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_secplus_v1_const.te_short * 2); + break; + case SECPLUS_V1_BIT_2: + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_secplus_v1_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_secplus_v1_const.te_short * 3); + break; + + default: + FURI_LOG_E(TAG, "Encoder error, wrong bit type"); + return false; + break; + } + } + + //Send header packet 2 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_secplus_v1_const.te_short * (116)); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_secplus_v1_const.te_short * 3); + + //Send data packet 2 + for(uint8_t i = SECPLUS_V1_PACKET_2_INDEX_BASE + 1; i < SECPLUS_V1_PACKET_2_INDEX_BASE + 21; + i++) { + switch(instance->data_array[i]) { + case SECPLUS_V1_BIT_0: + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_secplus_v1_const.te_short * 3); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_secplus_v1_const.te_short); + break; + case SECPLUS_V1_BIT_1: + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_secplus_v1_const.te_short * 2); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_secplus_v1_const.te_short * 2); + break; + case SECPLUS_V1_BIT_2: + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_secplus_v1_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_secplus_v1_const.te_short * 3); + break; + + default: + FURI_LOG_E(TAG, "Encoder error, wrong bit type."); + return false; + break; + } + } + + return true; +} + +/** + * Security+ 1.0 message encoding + * @param instance SubGhzProtocolEncoderSecPlus_v1* + */ + +static bool subghz_protocol_secplus_v1_encode(SubGhzProtocolEncoderSecPlus_v1* instance) { + uint32_t fixed = (instance->generic.data >> 32) & 0xFFFFFFFF; + uint32_t rolling = instance->generic.data & 0xFFFFFFFF; + + uint8_t rolling_array[20] = {0}; + uint8_t fixed_array[20] = {0}; + uint32_t acc = 0; + + //increment the counter + rolling += 2; + + //update data + instance->generic.data &= 0xFFFFFFFF00000000; + instance->generic.data |= rolling; + + if(rolling > 0xFFFFFFFF) { + rolling = 0xE6000000; + } + if(fixed > 0xCFD41B90) { + FURI_LOG_E("TAG", "Encode wrong fixed data"); + return false; + } + + rolling = subghz_protocol_blocks_reverse_key(rolling, 32); + + for(int i = 19; i > -1; i--) { + rolling_array[i] = rolling % 3; + rolling /= 3; + fixed_array[i] = fixed % 3; + fixed /= 3; + } + + instance->data_array[SECPLUS_V1_PACKET_1_INDEX_BASE] = SECPLUS_V1_PACKET_1_HEADER; + instance->data_array[SECPLUS_V1_PACKET_2_INDEX_BASE] = SECPLUS_V1_PACKET_2_HEADER; + + //encode packet 1 + for(uint8_t i = 1; i < 11; i++) { + acc += rolling_array[i - 1]; + instance->data_array[i * 2 - 1] = rolling_array[i - 1]; + acc += fixed_array[i - 1]; + instance->data_array[i * 2] = acc % 3; + } + + acc = 0; + //encode packet 2 + for(uint8_t i = 11; i < 21; i++) { + acc += rolling_array[i - 1]; + instance->data_array[i * 2] = rolling_array[i - 1]; + acc += fixed_array[i - 1]; + instance->data_array[i * 2 + 1] = acc % 3; + } + + return true; +} + +bool subghz_protocol_encoder_secplus_v1_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderSecPlus_v1* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_secplus_v1_encode(instance)) { + break; + } + if(!subghz_protocol_encoder_secplus_v1_get_upload(instance)) { + break; + } + + uint8_t key_data[sizeof(uint64_t)] = {0}; + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF; + } + if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Key"); + break; + } + + instance->encoder.is_runing = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_secplus_v1_stop(void* context) { + SubGhzProtocolEncoderSecPlus_v1* instance = context; + instance->encoder.is_runing = false; +} + +LevelDuration subghz_protocol_encoder_secplus_v1_yield(void* context) { + SubGhzProtocolEncoderSecPlus_v1* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_runing) { + instance->encoder.is_runing = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + void* subghz_protocol_decoder_secplus_v1_alloc(SubGhzEnvironment* environment) { UNUSED(environment); SubGhzProtocolDecoderSecPlus_v1* instance = malloc(sizeof(SubGhzProtocolDecoderSecPlus_v1)); @@ -110,7 +347,7 @@ void subghz_protocol_decoder_secplus_v1_reset(void* context) { } /** - * Security+ 1.0 half-message decoding + * Security+ 1.0 message decoding * @param instance SubGhzProtocolDecoderSecPlus_v1* */ @@ -291,6 +528,18 @@ bool subghz_protocol_decoder_secplus_v1_deserialize(void* context, FlipperFormat return subghz_block_generic_deserialize(&instance->generic, flipper_format); } +bool subghz_protocol_secplus_v1_check_fixed(uint32_t fixed) { + //uint8_t id0 = (fixed / 3) % 3; + uint8_t id1 = (fixed / 9) % 3; + uint8_t btn = fixed % 3; + + do { + if(id1 == 0) return false; + if(!(btn == 0 || btn == 1 || btn == 2)) return false; + } while(false); + return true; +} + void subghz_protocol_decoder_secplus_v1_get_string(void* context, string_t output) { furi_assert(context); SubGhzProtocolDecoderSecPlus_v1* instance = context; diff --git a/lib/subghz/protocols/secplus_v1.h b/lib/subghz/protocols/secplus_v1.h index 891f751c..1c752df7 100644 --- a/lib/subghz/protocols/secplus_v1.h +++ b/lib/subghz/protocols/secplus_v1.h @@ -10,6 +10,40 @@ extern const SubGhzProtocolDecoder subghz_protocol_secplus_v1_decoder; extern const SubGhzProtocolEncoder subghz_protocol_secplus_v1_encoder; extern const SubGhzProtocol subghz_protocol_secplus_v1; +/** + * Allocate SubGhzProtocolEncoderSecPlus_v1. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderSecPlus_v1* pointer to a SubGhzProtocolEncoderSecPlus_v1 instance + */ +void* subghz_protocol_encoder_secplus_v1_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderSecPlus_v1. + * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v1 instance + */ +void subghz_protocol_encoder_secplus_v1_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v1 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_secplus_v1_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v1 instance + */ +void subghz_protocol_encoder_secplus_v1_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v1 instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_secplus_v1_yield(void* context); + /** * Allocate SubGhzProtocolDecoderSecPlus_v1. * @param environment Pointer to a SubGhzEnvironment instance @@ -66,6 +100,13 @@ bool subghz_protocol_decoder_secplus_v1_serialize( */ bool subghz_protocol_decoder_secplus_v1_deserialize(void* context, FlipperFormat* flipper_format); +/** + * Validation of fixed parts SubGhzProtocolDecoderSecPlus_v1. + * @param fixed fixed parts + * @return true On success + */ +bool subghz_protocol_secplus_v1_check_fixed(uint32_t fixed); + /** * Getting a textual representation of the received data. * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v1 instance diff --git a/lib/subghz/protocols/secplus_v2.c b/lib/subghz/protocols/secplus_v2.c index 936eb0a6..70ff19e4 100644 --- a/lib/subghz/protocols/secplus_v2.c +++ b/lib/subghz/protocols/secplus_v2.c @@ -1,5 +1,6 @@ #include "secplus_v2.h" #include +#include #include "../blocks/const.h" #include "../blocks/decoder.h" #include "../blocks/encoder.h" @@ -42,6 +43,7 @@ struct SubGhzProtocolEncoderSecPlus_v2 { SubGhzProtocolBlockEncoder encoder; SubGhzBlockGeneric generic; + uint64_t secplus_packet_1; }; typedef enum { @@ -63,23 +65,555 @@ const SubGhzProtocolDecoder subghz_protocol_secplus_v2_decoder = { }; const SubGhzProtocolEncoder subghz_protocol_secplus_v2_encoder = { - .alloc = NULL, - .free = NULL, + .alloc = subghz_protocol_encoder_secplus_v2_alloc, + .free = subghz_protocol_encoder_secplus_v2_free, - .deserialize = NULL, - .stop = NULL, - .yield = NULL, + .deserialize = subghz_protocol_encoder_secplus_v2_deserialize, + .stop = subghz_protocol_encoder_secplus_v2_stop, + .yield = subghz_protocol_encoder_secplus_v2_yield, }; const SubGhzProtocol subghz_protocol_secplus_v2 = { .name = SUBGHZ_PROTOCOL_SECPLUS_V2_NAME, .type = SubGhzProtocolTypeDynamic, - .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Send, .decoder = &subghz_protocol_secplus_v2_decoder, .encoder = &subghz_protocol_secplus_v2_encoder, }; +void* subghz_protocol_encoder_secplus_v2_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderSecPlus_v2* instance = malloc(sizeof(SubGhzProtocolEncoderSecPlus_v2)); + + instance->base.protocol = &subghz_protocol_secplus_v2; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 256; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_runing = false; + return instance; +} + +void subghz_protocol_encoder_secplus_v2_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderSecPlus_v2* instance = context; + free(instance->encoder.upload); + free(instance); +} + +static bool subghz_protocol_secplus_v2_mix_invet(uint8_t invert, uint16_t p[]) { + // selectively invert buffers + switch(invert) { + case 0x00: // 0b0000 (True, True, False), + p[0] = ~p[0] & 0x03FF; + p[1] = ~p[1] & 0x03FF; + break; + case 0x01: // 0b0001 (False, True, False), + p[1] = ~p[1] & 0x03FF; + break; + case 0x02: // 0b0010 (False, False, True), + p[2] = ~p[2] & 0x03FF; + break; + case 0x04: // 0b0100 (True, True, True), + p[0] = ~p[0] & 0x03FF; + p[1] = ~p[1] & 0x03FF; + p[2] = ~p[2] & 0x03FF; + break; + case 0x05: // 0b0101 (True, False, True), + case 0x0a: // 0b1010 (True, False, True), + p[0] = ~p[0] & 0x03FF; + p[2] = ~p[2] & 0x03FF; + break; + case 0x06: // 0b0110 (False, True, True), + p[1] = ~p[1] & 0x03FF; + p[2] = ~p[2] & 0x03FF; + break; + case 0x08: // 0b1000 (True, False, False), + p[0] = ~p[0] & 0x03FF; + break; + case 0x09: // 0b1001 (False, False, False), + break; + default: + FURI_LOG_E(TAG, "Invert FAIL"); + return false; + } + return true; +} + +static bool subghz_protocol_secplus_v2_mix_order_decode(uint8_t order, uint16_t p[]) { + uint16_t a = p[0], b = p[1], c = p[2]; + + // selectively reorder buffers + switch(order) { + case 0x06: // 0b0110 2, 1, 0], + case 0x09: // 0b1001 2, 1, 0], + p[2] = a; + p[1] = b; + p[0] = c; + break; + case 0x08: // 0b1000 1, 2, 0], + case 0x04: // 0b0100 1, 2, 0], + p[1] = a; + p[2] = b; + p[0] = c; + break; + case 0x01: // 0b0001 2, 0, 1], + p[2] = a; + p[0] = b; + p[1] = c; + break; + case 0x00: // 0b0000 0, 2, 1], + p[0] = a; + p[2] = b; + p[1] = c; + break; + case 0x05: // 0b0101 1, 0, 2], + p[1] = a; + p[0] = b; + p[2] = c; + break; + case 0x02: // 0b0010 0, 1, 2], + case 0x0A: // 0b1010 0, 1, 2], + p[0] = a; + p[1] = b; + p[2] = c; + break; + default: + FURI_LOG_E(TAG, "Order FAIL"); + return false; + } + return true; +} + +static bool subghz_protocol_secplus_v2_mix_order_encode(uint8_t order, uint16_t p[]) { + uint16_t a, b, c; + + // selectively reorder buffers + switch(order) { + case 0x06: // 0b0110 2, 1, 0], + case 0x09: // 0b1001 2, 1, 0], + a = p[2]; + b = p[1]; + c = p[0]; + break; + case 0x08: // 0b1000 1, 2, 0], + case 0x04: // 0b0100 1, 2, 0], + a = p[1]; + b = p[2]; + c = p[0]; + break; + case 0x01: // 0b0001 2, 0, 1], + a = p[2]; + b = p[0]; + c = p[1]; + break; + case 0x00: // 0b0000 0, 2, 1], + a = p[0]; + b = p[2]; + c = p[1]; + break; + case 0x05: // 0b0101 1, 0, 2], + a = p[1]; + b = p[0]; + c = p[2]; + break; + case 0x02: // 0b0010 0, 1, 2], + case 0x0A: // 0b1010 0, 1, 2], + a = p[0]; + b = p[1]; + c = p[2]; + break; + default: + FURI_LOG_E(TAG, "Order FAIL"); + return false; + } + + p[0] = a; + p[1] = b; + p[2] = c; + return true; +} + +/** + * Security+ 2.0 half-message decoding + * @param data data + * @param roll_array[] return roll_array part + * @param fixed[] return fixed part + * @return true On success + */ + +static bool + subghz_protocol_secplus_v2_decode_half(uint64_t data, uint8_t roll_array[], uint32_t* fixed) { + uint8_t order = (data >> 34) & 0x0f; + uint8_t invert = (data >> 30) & 0x0f; + uint16_t p[3] = {0}; + + for(int i = 29; i >= 0; i -= 3) { + p[0] = p[0] << 1 | bit_read(data, i); + p[1] = p[1] << 1 | bit_read(data, i - 1); + p[2] = p[2] << 1 | bit_read(data, i - 2); + } + + if(!subghz_protocol_secplus_v2_mix_invet(invert, p)) return false; + if(!subghz_protocol_secplus_v2_mix_order_decode(order, p)) return false; + + data = order << 4 | invert; + int k = 0; + for(int i = 6; i >= 0; i -= 2) { + roll_array[k++] = (data >> i) & 0x03; + if(roll_array[k] == 3) { + FURI_LOG_E(TAG, "Roll_Array FAIL"); + return false; + } + } + + for(int i = 8; i >= 0; i -= 2) { + roll_array[k++] = (p[2] >> i) & 0x03; + if(roll_array[k] == 3) { + FURI_LOG_E(TAG, "Roll_Array FAIL"); + return false; + } + } + + fixed[0] = p[0] << 10 | p[1]; + return true; +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param packet_1 first part of the message + */ +static void + subghz_protocol_secplus_v2_remote_controller(SubGhzBlockGeneric* instance, uint64_t packet_1) { + uint32_t fixed_1[1]; + uint8_t roll_1[9] = {0}; + uint32_t fixed_2[1]; + uint8_t roll_2[9] = {0}; + uint8_t rolling_digits[18] = {0}; + + if(subghz_protocol_secplus_v2_decode_half(packet_1, roll_1, fixed_1) && + subghz_protocol_secplus_v2_decode_half(instance->data, roll_2, fixed_2)) { + rolling_digits[0] = roll_2[8]; + rolling_digits[1] = roll_1[8]; + + rolling_digits[2] = roll_2[4]; + rolling_digits[3] = roll_2[5]; + rolling_digits[4] = roll_2[6]; + rolling_digits[5] = roll_2[7]; + + rolling_digits[6] = roll_1[4]; + rolling_digits[7] = roll_1[5]; + rolling_digits[8] = roll_1[6]; + rolling_digits[9] = roll_1[7]; + + rolling_digits[10] = roll_2[0]; + rolling_digits[11] = roll_2[1]; + rolling_digits[12] = roll_2[2]; + rolling_digits[13] = roll_2[3]; + + rolling_digits[14] = roll_1[0]; + rolling_digits[15] = roll_1[1]; + rolling_digits[16] = roll_1[2]; + rolling_digits[17] = roll_1[3]; + + uint32_t rolling = 0; + for(int i = 0; i < 18; i++) { + rolling = (rolling * 3) + rolling_digits[i]; + } + // Max value = 2^28 (268435456) + if(rolling >= 0x10000000) { + FURI_LOG_E(TAG, "Rolling FAIL"); + instance->cnt = 0; + instance->btn = 0; + instance->serial = 0; + } else { + instance->cnt = subghz_protocol_blocks_reverse_key(rolling, 28); + instance->btn = fixed_1[0] >> 12; + instance->serial = fixed_1[0] << 20 | fixed_2[0]; + } + } else { + instance->cnt = 0; + instance->btn = 0; + instance->serial = 0; + } +} + +/** + * Security+ 2.0 half-message encoding + * @param roll_array[] roll_array part + * @param fixed[] fixed part + * @return return data + */ + +static uint64_t subghz_protocol_secplus_v2_encode_half(uint8_t roll_array[], uint32_t fixed) { + uint64_t data = 0; + uint16_t p[3] = {(fixed >> 10) & 0x3FF, fixed & 0x3FF, 0}; + uint8_t order = roll_array[0] << 2 | roll_array[1]; + uint8_t invert = roll_array[2] << 2 | roll_array[3]; + p[2] = (uint16_t)roll_array[4] << 8 | roll_array[5] << 6 | roll_array[6] << 4 | + roll_array[7] << 2 | roll_array[8]; + + if(!subghz_protocol_secplus_v2_mix_order_encode(order, p)) return 0; + if(!subghz_protocol_secplus_v2_mix_invet(invert, p)) return 0; + + for(int i = 0; i < 10; i++) { + data <<= 3; + data |= bit_read(p[0], 9 - i) << 2 | bit_read(p[1], 9 - i) << 1 | bit_read(p[2], 9 - i); + } + data |= ((uint64_t)order) << 34 | ((uint64_t)invert) << 30; + + return data; +} + +/** + * Security+ 2.0 message encoding + * @param instance SubGhzProtocolEncoderSecPlus_v2* + */ + +static void subghz_protocol_secplus_v2_encode(SubGhzProtocolEncoderSecPlus_v2* instance) { + uint32_t fixed_1[1] = {instance->generic.btn << 12 | instance->generic.serial >> 20}; + uint32_t fixed_2[1] = {instance->generic.serial & 0xFFFFF}; + uint8_t rolling_digits[18] = {0}; + uint8_t roll_1[9] = {0}; + uint8_t roll_2[9] = {0}; + + instance->generic.cnt++; + //ToDo it is not known what value the counter starts + if(instance->generic.cnt > 0xFFFFFFF) instance->generic.cnt = 0xE500000; + uint32_t rolling = subghz_protocol_blocks_reverse_key(instance->generic.cnt, 28); + + for(int8_t i = 17; i > -1; i--) { + rolling_digits[i] = rolling % 3; + rolling /= 3; + } + + roll_2[8] = rolling_digits[0]; + roll_1[8] = rolling_digits[1]; + + roll_2[4] = rolling_digits[2]; + roll_2[5] = rolling_digits[3]; + roll_2[6] = rolling_digits[4]; + roll_2[7] = rolling_digits[5]; + + roll_1[4] = rolling_digits[6]; + roll_1[5] = rolling_digits[7]; + roll_1[6] = rolling_digits[8]; + roll_1[7] = rolling_digits[9]; + + roll_2[0] = rolling_digits[10]; + roll_2[1] = rolling_digits[11]; + roll_2[2] = rolling_digits[12]; + roll_2[3] = rolling_digits[13]; + + roll_1[0] = rolling_digits[14]; + roll_1[1] = rolling_digits[15]; + roll_1[2] = rolling_digits[16]; + roll_1[3] = rolling_digits[17]; + + instance->secplus_packet_1 = SECPLUS_V2_HEADER | SECPLUS_V2_PACKET_1 | + subghz_protocol_secplus_v2_encode_half(roll_1, fixed_1[0]); + instance->generic.data = SECPLUS_V2_HEADER | SECPLUS_V2_PACKET_2 | + subghz_protocol_secplus_v2_encode_half(roll_2, fixed_2[0]); +} + +static LevelDuration + subghz_protocol_encoder_secplus_v2_add_duration_to_upload(ManchesterEncoderResult result) { + LevelDuration data = {.duration = 0, .level = 0}; + switch(result) { + case ManchesterEncoderResultShortLow: + data.duration = subghz_protocol_secplus_v2_const.te_short; + data.level = false; + break; + case ManchesterEncoderResultLongLow: + data.duration = subghz_protocol_secplus_v2_const.te_long; + data.level = false; + break; + case ManchesterEncoderResultLongHigh: + data.duration = subghz_protocol_secplus_v2_const.te_long; + data.level = true; + break; + case ManchesterEncoderResultShortHigh: + data.duration = subghz_protocol_secplus_v2_const.te_short; + data.level = true; + break; + + default: + furi_crash("SubGhz: ManchesterEncoderResult is incorrect."); + break; + } + return level_duration_make(data.level, data.duration); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderSecPlus_v2 instance + */ +static void + subghz_protocol_encoder_secplus_v2_get_upload(SubGhzProtocolEncoderSecPlus_v2* instance) { + furi_assert(instance); + size_t index = 0; + + ManchesterEncoderState enc_state; + manchester_encoder_reset(&enc_state); + ManchesterEncoderResult result; + + //Send data packet 1 + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(!manchester_encoder_advance( + &enc_state, bit_read(instance->secplus_packet_1, i - 1), &result)) { + instance->encoder.upload[index++] = + subghz_protocol_encoder_secplus_v2_add_duration_to_upload(result); + manchester_encoder_advance( + &enc_state, bit_read(instance->secplus_packet_1, i - 1), &result); + } + instance->encoder.upload[index++] = + subghz_protocol_encoder_secplus_v2_add_duration_to_upload(result); + } + instance->encoder.upload[index] = subghz_protocol_encoder_secplus_v2_add_duration_to_upload( + manchester_encoder_finish(&enc_state)); + if(level_duration_get_level(instance->encoder.upload[index])) { + index++; + } + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_secplus_v2_const.te_long * 136); + + //Send data packet 2 + manchester_encoder_reset(&enc_state); + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(!manchester_encoder_advance( + &enc_state, bit_read(instance->generic.data, i - 1), &result)) { + instance->encoder.upload[index++] = + subghz_protocol_encoder_secplus_v2_add_duration_to_upload(result); + manchester_encoder_advance( + &enc_state, bit_read(instance->generic.data, i - 1), &result); + } + instance->encoder.upload[index++] = + subghz_protocol_encoder_secplus_v2_add_duration_to_upload(result); + } + instance->encoder.upload[index] = subghz_protocol_encoder_secplus_v2_add_duration_to_upload( + manchester_encoder_finish(&enc_state)); + if(level_duration_get_level(instance->encoder.upload[index])) { + index++; + } + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_secplus_v2_const.te_long * 136); + + instance->encoder.size_upload = index; +} + +bool subghz_protocol_encoder_secplus_v2_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderSecPlus_v2* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + + uint8_t key_data[sizeof(uint64_t)] = {0}; + if(!flipper_format_read_hex( + flipper_format, "Secplus_packet_1", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Secplus_packet_1"); + break; + } + for(uint8_t i = 0; i < sizeof(uint64_t); i++) { + instance->secplus_packet_1 = instance->secplus_packet_1 << 8 | key_data[i]; + } + + subghz_protocol_secplus_v2_remote_controller( + &instance->generic, instance->secplus_packet_1); + subghz_protocol_secplus_v2_encode(instance); + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + subghz_protocol_encoder_secplus_v2_get_upload(instance); + + //update data + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF; + } + if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Key"); + break; + } + + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->secplus_packet_1 >> i * 8) & 0xFF; + } + if(!flipper_format_update_hex( + flipper_format, "Secplus_packet_1", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Secplus_packet_1"); + break; + } + + instance->encoder.is_runing = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_secplus_v2_stop(void* context) { + SubGhzProtocolEncoderSecPlus_v2* instance = context; + instance->encoder.is_runing = false; +} + +LevelDuration subghz_protocol_encoder_secplus_v2_yield(void* context) { + SubGhzProtocolEncoderSecPlus_v2* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_runing) { + instance->encoder.is_runing = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +bool subghz_protocol_secplus_v2_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint32_t cnt, + uint32_t frequency, + FuriHalSubGhzPreset preset) { + furi_assert(context); + SubGhzProtocolEncoderSecPlus_v2* instance = context; + instance->generic.serial = serial; + instance->generic.cnt = cnt; + instance->generic.btn = btn; + instance->generic.data_count_bit = + (uint8_t)subghz_protocol_secplus_v2_const.min_count_bit_for_found; + subghz_protocol_secplus_v2_encode(instance); + bool res = + subghz_block_generic_serialize(&instance->generic, flipper_format, frequency, preset); + + uint8_t key_data[sizeof(uint64_t)] = {0}; + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->secplus_packet_1 >> i * 8) & 0xFF; + } + + if(res && + !flipper_format_write_hex(flipper_format, "Secplus_packet_1", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Secplus_packet_1"); + res = false; + } + return res; +} + void* subghz_protocol_decoder_secplus_v2_alloc(SubGhzEnvironment* environment) { UNUSED(environment); SubGhzProtocolDecoderSecPlus_v2* instance = malloc(sizeof(SubGhzProtocolDecoderSecPlus_v2)); @@ -213,186 +747,6 @@ void subghz_protocol_decoder_secplus_v2_feed(void* context, bool level, uint32_t } } -/** - * Security+ 2.0 half-message decoding - * @param data data - * @param roll_array[] return roll_array part - * @param fixed[] return fixed part - * @return true On success - */ - -static bool - subghz_protocol_secplus_v2_decode_half(uint64_t data, uint8_t roll_array[], uint32_t* fixed) { - uint8_t order = (data >> 34) & 0x0f; - uint8_t invert = (data >> 30) & 0x0f; - uint16_t p[3] = {0}; - - for(int i = 29; i >= 0; i -= 3) { - p[0] = p[0] << 1 | bit_read(data, i); - p[1] = p[1] << 1 | bit_read(data, i - 1); - p[2] = p[2] << 1 | bit_read(data, i - 2); - } - - // selectively invert buffers - switch(invert) { - case 0x00: // 0b0000 (True, True, False), - p[0] = ~p[0] & 0x03FF; - p[1] = ~p[1] & 0x03FF; - break; - case 0x01: // 0b0001 (False, True, False), - p[1] = ~p[1] & 0x03FF; - break; - case 0x02: // 0b0010 (False, False, True), - p[2] = ~p[2] & 0x03FF; - break; - case 0x04: // 0b0100 (True, True, True), - p[0] = ~p[0] & 0x03FF; - p[1] = ~p[1] & 0x03FF; - p[2] = ~p[2] & 0x03FF; - break; - case 0x05: // 0b0101 (True, False, True), - case 0x0a: // 0b1010 (True, False, True), - p[0] = ~p[0] & 0x03FF; - p[2] = ~p[2] & 0x03FF; - break; - case 0x06: // 0b0110 (False, True, True), - p[1] = ~p[1] & 0x03FF; - p[2] = ~p[2] & 0x03FF; - break; - case 0x08: // 0b1000 (True, False, False), - p[0] = ~p[0] & 0x03FF; - break; - case 0x09: // 0b1001 (False, False, False), - break; - default: - FURI_LOG_E(TAG, "Invert FAIL"); - return false; - } - - uint16_t a = p[0], b = p[1], c = p[2]; - - // selectively reorder buffers - switch(order) { - case 0x06: // 0b0110 2, 1, 0], - case 0x09: // 0b1001 2, 1, 0], - p[2] = a; - p[1] = b; - p[0] = c; - break; - case 0x08: // 0b1000 1, 2, 0], - case 0x04: // 0b0100 1, 2, 0], - p[1] = a; - p[2] = b; - p[0] = c; - break; - case 0x01: // 0b0001 2, 0, 1], - p[2] = a; - p[0] = b; - p[1] = c; - break; - case 0x00: // 0b0000 0, 2, 1], - p[0] = a; - p[2] = b; - p[1] = c; - break; - case 0x05: // 0b0101 1, 0, 2], - p[1] = a; - p[0] = b; - p[2] = c; - break; - case 0x02: // 0b0010 0, 1, 2], - case 0x0A: // 0b1010 0, 1, 2], - p[0] = a; - p[1] = b; - p[2] = c; - break; - default: - FURI_LOG_E(TAG, "Order FAIL"); - return false; - } - - data = order << 4 | invert; - int k = 0; - for(int i = 6; i >= 0; i -= 2) { - roll_array[k++] = (data >> i) & 0x03; - if(roll_array[k] == 3) { - FURI_LOG_E(TAG, "Roll_Array FAIL"); - return false; - } - } - - for(int i = 8; i >= 0; i -= 2) { - roll_array[k++] = (p[2] >> i) & 0x03; - if(roll_array[k] == 3) { - FURI_LOG_E(TAG, "Roll_Array FAIL"); - return false; - } - } - - fixed[0] = p[0] << 10 | p[1]; - return true; -} - -/** - * Analysis of received data - * @param instance Pointer to a SubGhzBlockGeneric* instance - * @param packet_1 first part of the message - */ -static void - subghz_protocol_secplus_v2_remote_controller(SubGhzBlockGeneric* instance, uint64_t packet_1) { - uint32_t fixed_1[1]; - uint8_t roll_1[9] = {0}; - uint32_t fixed_2[1]; - uint8_t roll_2[9] = {0}; - uint8_t rolling_digits[18] = {0}; - - if(subghz_protocol_secplus_v2_decode_half(packet_1, roll_1, fixed_1) && - subghz_protocol_secplus_v2_decode_half(instance->data, roll_2, fixed_2)) { - rolling_digits[0] = roll_2[8]; - rolling_digits[1] = roll_1[8]; - - rolling_digits[2] = roll_2[4]; - rolling_digits[3] = roll_2[5]; - rolling_digits[4] = roll_2[6]; - rolling_digits[5] = roll_2[7]; - - rolling_digits[6] = roll_1[4]; - rolling_digits[7] = roll_1[5]; - rolling_digits[8] = roll_1[6]; - rolling_digits[9] = roll_1[7]; - - rolling_digits[10] = roll_2[0]; - rolling_digits[11] = roll_2[1]; - rolling_digits[12] = roll_2[2]; - rolling_digits[13] = roll_2[3]; - - rolling_digits[14] = roll_1[0]; - rolling_digits[15] = roll_1[1]; - rolling_digits[16] = roll_1[2]; - rolling_digits[17] = roll_1[3]; - - uint32_t rolling = 0; - for(int i = 0; i < 18; i++) { - rolling = (rolling * 3) + rolling_digits[i]; - } - // Max value = 2^28 (268435456) - if(rolling >= 0x10000000) { - FURI_LOG_E(TAG, "Rolling FAIL"); - instance->cnt = 0; - instance->btn = 0; - instance->serial = 0; - } else { - instance->cnt = subghz_protocol_blocks_reverse_key(rolling, 28); - instance->btn = fixed_1[0] >> 12; - instance->serial = fixed_1[0] << 20 | fixed_2[0]; - } - } else { - instance->cnt = 0; - instance->btn = 0; - instance->serial = 0; - } -} - uint8_t subghz_protocol_decoder_secplus_v2_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderSecPlus_v2* instance = context; diff --git a/lib/subghz/protocols/secplus_v2.h b/lib/subghz/protocols/secplus_v2.h index dbb1e1bd..3d695148 100644 --- a/lib/subghz/protocols/secplus_v2.h +++ b/lib/subghz/protocols/secplus_v2.h @@ -10,6 +10,61 @@ extern const SubGhzProtocolDecoder subghz_protocol_secplus_v2_decoder; extern const SubGhzProtocolEncoder subghz_protocol_secplus_v2_encoder; extern const SubGhzProtocol subghz_protocol_secplus_v2; +/** + * Allocate SubGhzProtocolEncoderSecPlus_v2. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderSecPlus_v2* pointer to a SubGhzProtocolEncoderSecPlus_v2 instance + */ +void* subghz_protocol_encoder_secplus_v2_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderSecPlus_v2. + * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v2 instance + */ +void subghz_protocol_encoder_secplus_v2_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v2 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_secplus_v2_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v2 instance + */ +void subghz_protocol_encoder_secplus_v2_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v2 instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_secplus_v2_yield(void* context); + +/** + * Key generation from simple data. + * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v2 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number, 32 bit + * @param btn Button number, 8 bit + * @param cnt Container value, 28 bit + * @param manufacture_name Name of manufacturer's key + * @param frequency Transmission frequency, Hz + * @param preset Modulation, FuriHalSubGhzPreset + * @return true On success + */ +bool subghz_protocol_secplus_v2_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint32_t cnt, + uint32_t frequency, + FuriHalSubGhzPreset preset); + /** * Allocate SubGhzProtocolDecoderSecPlus_v2. * @param environment Pointer to a SubGhzEnvironment instance diff --git a/lib/toolbox/hex.c b/lib/toolbox/hex.c index 739eb30b..41bb24bb 100644 --- a/lib/toolbox/hex.c +++ b/lib/toolbox/hex.c @@ -26,3 +26,14 @@ bool hex_chars_to_uint8(char hi, char low, uint8_t* value) { return false; } } + +bool hex_chars_to_uint64(const char* value_str, uint64_t* value) { + uint8_t* _value = (uint8_t*)value; + bool parse_success = false; + + for(uint8_t i = 0; i < 8; i++) { + parse_success = hex_chars_to_uint8(value_str[i * 2], value_str[i * 2 + 1], &_value[7 - i]); + if(!parse_success) break; + } + return parse_success; +} diff --git a/lib/toolbox/hex.h b/lib/toolbox/hex.h index ca10f2be..c6683a52 100644 --- a/lib/toolbox/hex.h +++ b/lib/toolbox/hex.h @@ -6,23 +6,31 @@ extern "C" { #endif -/** - * Convert ASCII hex value to nibble - * @param c ASCII character - * @param nibble nibble pointer, output - * @return bool conversion status +/** 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); -/** - * Convert ASCII hex values to byte - * @param hi hi nibble text - * @param low low nibble text - * @param value output value - * @return bool conversion status +/** Convert ASCII hex values to byte + * @param hi hi nibble text + * @param low low nibble text + * @param value output value + * + * @return bool conversion status */ bool hex_chars_to_uint8(char hi, char low, uint8_t* value); +/** Convert ASCII hex values to uint64_t + * @param value_str ASCII 64 bi data + * @param value output value + * + * @return bool conversion status + */ +bool hex_chars_to_uint64(const char* value_str, uint64_t* value); + #ifdef __cplusplus } #endif diff --git a/lib/toolbox/path.c b/lib/toolbox/path.c index 4fd042e4..a99e57d1 100644 --- a/lib/toolbox/path.c +++ b/lib/toolbox/path.c @@ -19,6 +19,20 @@ void path_extract_filename_no_ext(const char* path, string_t filename) { string_mid(filename, start_position, end_position - start_position); } +void path_extract_filename(string_t path, string_t name, bool trim_ext) { + size_t filename_start = string_search_rchar(path, '/'); + if(filename_start > 0) { + filename_start++; + string_set_n(name, path, filename_start, string_size(path) - filename_start); + } + if(trim_ext) { + size_t dot = string_search_rchar(name, '.'); + if(dot > 0) { + string_left(name, dot); + } + } +} + static inline void path_cleanup(string_t path) { string_strim(path); while(string_end_with_str_p(path, "/")) { diff --git a/lib/toolbox/path.h b/lib/toolbox/path.h index 0de63bb2..76e501cc 100644 --- a/lib/toolbox/path.h +++ b/lib/toolbox/path.h @@ -14,6 +14,15 @@ extern "C" { */ void path_extract_filename_no_ext(const char* path, string_t filename); +/** + * @brief Extract filename string from path. + * + * @param path path string + * @param filename output filename string. Must be initialized before. + * @param trim_ext true - get filename without extension + */ +void path_extract_filename(string_t path, string_t filename, bool trim_ext); + /** * @brief Extract last path component *